From: stephen@cubedesigners.com
Date: Thu, 25 Feb 2021 16:13:08 +0000 (+0000)
Subject: Add WP Rocket and Imagify plugins.
X-Git-Url: http://git.cubedesigners.com/?a=commitdiff_plain;h=532c52ef4156fa4d255f64d923bf05f9beed29af;p=physioassist-wordpress.git
Add WP Rocket and Imagify plugins.
---
diff --git a/wp-content/plugins/imagify/assets/css/admin-bar.css b/wp-content/plugins/imagify/assets/css/admin-bar.css
new file mode 100644
index 00000000..67529f32
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/admin-bar.css
@@ -0,0 +1,162 @@
+.imagify-account,
+.imagify-account-link {
+ padding-right: 15px;
+}
+.imagify-meteo-icon {
+ display: inline-block;
+ height: 38px;
+ vertical-align: middle;
+ margin-right: 10px;
+}
+.imagify-user-plan {
+ color: #40b1d0;
+}
+.imagify-meteo-title.imagify-meteo-title {
+ color: #FFF;
+ font-size: 17px;
+}
+.imagify-space-left > p {
+ color: #FFF;
+}
+#wp-admin-bar-imagify-profile [class^="imagify-bar-"] {
+ position: relative;
+ height: 1.5em;
+ width: 100%;
+ background: #60758D;
+ color: #FFF;
+ font-size: 10px;
+}
+#wp-admin-bar-imagify-profile .imagify-progress {
+ height: 1.5em;
+ font-size: 1em;
+}
+.imagify-progress {
+ transition: width .3s;
+}
+.imagify-bar-positive .imagify-progress {
+ background: #8CC152;
+}
+.imagify-bar-positive .imagify-barnb {
+ color: #8CC152;
+}
+.imagify-bar-negative .imagify-progress {
+ background: #73818C;
+}
+.imagify-bar-negative .imagify-barnb {
+ color: #73818C;
+}
+.imagify-bar-neutral .imagify-progress {
+ background: #F5A623;
+}
+.imagify-space-left .imagify-bar-negative .imagify-progress {
+ background: #D0021B;
+}
+
+#wpadminbar #wp-admin-bar-imagify-profile * {
+ line-height: 1.5;
+ white-space: initial;
+}
+#wpadminbar #wp-admin-bar-imagify .ab-submenu {
+ padding-bottom: 0;
+}
+#wpadminbar #wp-admin-bar-imagify-profile .ab-item {
+ height: auto;
+ padding: 0 13px;
+}
+#wpadminbar #wp-admin-bar-imagify-profile {
+ min-width: 200px;
+ padding: 15px 0 10px;
+ margin-top: 0.7em;
+ background: #222;
+}
+#wp-admin-bar-imagify .dashicons {
+ font-family: "dashicons";
+ font-size: 18px;
+ vertical-align: middle;
+ margin: 0 5px 0 0;
+}
+#wp-admin-bar-imagify .button-text {
+ display: inline-block;
+ vertical-align: middle;
+}
+#wp-admin-bar-imagify .imagify-abq-row {
+ display: table;
+ width: 100%;
+}
+#wp-admin-bar-imagify .imagify-abq-row + .imagify-abq-row {
+ margin-top: .75em;
+}
+#wp-admin-bar-imagify .imagify-abq-row > * {
+ display: table-cell;
+}
+#wp-admin-bar-imagify-profile .imagify-meteo-icon {
+ padding-right: 7px;
+}
+#wp-admin-bar-imagify-profile .imagify-meteo-icon img {
+ width: 37px;
+}
+#wp-admin-bar-imagify-profile .imagify-meteo-title {
+ font-size: 17px;
+}
+#wp-admin-bar-imagify-profile .imagify-meteo-subs {
+ color: #72889F;
+}
+#wpadminbar #wp-admin-bar-imagify-profile strong {
+ font-weight: bold;
+}
+#wpadminbar #wp-admin-bar-imagify-profile .imagify-user-plan,
+#wpadminbar #wp-admin-bar-imagify-profile a {
+ padding: 0;
+ color: #40B1D0;
+}
+#wpadminbar #wp-admin-bar-imagify-profile .imagify-account-link {
+ display: table;
+}
+#wpadminbar #wp-admin-bar-imagify-profile .imagify-account-link > * {
+ display: table-cell;
+}
+#wpadminbar #wp-admin-bar-imagify-profile .imagify-space-left {
+ max-width: 210px;
+ min-width: 210px;
+ width: 210px;
+}
+#wpadminbar #wp-admin-bar-imagify-profile .imagify-space-left p {
+ font-size: 12px;
+}
+#wp-admin-bar-imagify-profile .imagify-error,
+#wp-admin-bar-imagify-profile .imagify-warning {
+ padding: 10px;
+ margin: 0 -13px -13px;
+}
+#wp-admin-bar-imagify-profile .imagify-error p + p,
+#wp-admin-bar-imagify-profile .imagify-warning p + p {
+ margin-top: .5em;
+}
+#wp-admin-bar-imagify-profile .imagify-error p + p + p,
+#wp-admin-bar-imagify-profile .imagify-warning p + p + p {
+ margin-top: 1em;
+}
+
+#wpadminbar #wp-admin-bar-imagify-profile .imagify-btn-ghost {
+ display: inline-block;
+ height: auto;
+ padding: 7px 10px;
+ border: 1px solid #FFF;
+ text-align: center;
+ background: transparent;
+ color: #FFF;
+ border-radius: 3px;
+ transition: all .275s;
+}
+
+#wpadminbar #wp-admin-bar-imagify-profile .imagify-btn-ghost:hover,
+#wpadminbar #wp-admin-bar-imagify-profile .imagify-btn-ghost:focus {
+ background: #FFF;
+ color: #888;
+}
+
+#wpadminbar .imagify-warning * {
+ background: #f5a623;
+ color: #FFF;
+ text-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
+}
diff --git a/wp-content/plugins/imagify/assets/css/admin-bar.min.css b/wp-content/plugins/imagify/assets/css/admin-bar.min.css
new file mode 100644
index 00000000..ef0843ea
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/admin-bar.min.css
@@ -0,0 +1 @@
+#wp-admin-bar-imagify .button-text,.imagify-meteo-icon{display:inline-block;vertical-align:middle}.imagify-account,.imagify-account-link{padding-right:15px}.imagify-meteo-icon{height:38px;margin-right:10px}.imagify-user-plan{color:#40b1d0}.imagify-meteo-title.imagify-meteo-title{color:#FFF;font-size:17px}.imagify-space-left>p{color:#FFF}#wp-admin-bar-imagify-profile [class^=imagify-bar-]{position:relative;height:1.5em;width:100%;background:#60758D;color:#FFF;font-size:10px}#wp-admin-bar-imagify-profile .imagify-progress{height:1.5em;font-size:1em}.imagify-progress{-webkit-transition:width .3s;-o-transition:width .3s;transition:width .3s}.imagify-bar-positive .imagify-progress{background:#8CC152}.imagify-bar-positive .imagify-barnb{color:#8CC152}.imagify-bar-negative .imagify-progress{background:#73818C}.imagify-bar-negative .imagify-barnb{color:#73818C}.imagify-bar-neutral .imagify-progress{background:#F5A623}.imagify-space-left .imagify-bar-negative .imagify-progress{background:#D0021B}#wpadminbar #wp-admin-bar-imagify-profile *{line-height:1.5;white-space:initial}#wpadminbar #wp-admin-bar-imagify .ab-submenu{padding-bottom:0}#wpadminbar #wp-admin-bar-imagify-profile .ab-item{height:auto;padding:0 13px}#wpadminbar #wp-admin-bar-imagify-profile{min-width:200px;padding:15px 0 10px;margin-top:.7em;background:#222}#wp-admin-bar-imagify .dashicons{font-family:dashicons;font-size:18px;vertical-align:middle;margin:0 5px 0 0}#wp-admin-bar-imagify .imagify-abq-row{display:table;width:100%}#wp-admin-bar-imagify .imagify-abq-row+.imagify-abq-row{margin-top:.75em}#wp-admin-bar-imagify .imagify-abq-row>*{display:table-cell}#wp-admin-bar-imagify-profile .imagify-meteo-icon{padding-right:7px}#wp-admin-bar-imagify-profile .imagify-meteo-icon img{width:37px}#wp-admin-bar-imagify-profile .imagify-meteo-title{font-size:17px}#wp-admin-bar-imagify-profile .imagify-meteo-subs{color:#72889F}#wpadminbar #wp-admin-bar-imagify-profile strong{font-weight:700}#wpadminbar #wp-admin-bar-imagify-profile .imagify-user-plan,#wpadminbar #wp-admin-bar-imagify-profile a{padding:0;color:#40B1D0}#wpadminbar #wp-admin-bar-imagify-profile .imagify-account-link{display:table}#wpadminbar #wp-admin-bar-imagify-profile .imagify-account-link>*{display:table-cell}#wpadminbar #wp-admin-bar-imagify-profile .imagify-space-left{max-width:210px;min-width:210px;width:210px}#wpadminbar #wp-admin-bar-imagify-profile .imagify-space-left p{font-size:12px}#wp-admin-bar-imagify-profile .imagify-error,#wp-admin-bar-imagify-profile .imagify-warning{padding:10px;margin:0 -13px -13px}#wp-admin-bar-imagify-profile .imagify-error p+p,#wp-admin-bar-imagify-profile .imagify-warning p+p{margin-top:.5em}#wp-admin-bar-imagify-profile .imagify-error p+p+p,#wp-admin-bar-imagify-profile .imagify-warning p+p+p{margin-top:1em}#wpadminbar #wp-admin-bar-imagify-profile .imagify-btn-ghost{display:inline-block;height:auto;padding:7px 10px;border:1px solid #FFF;text-align:center;background:0 0;color:#FFF;border-radius:3px;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}#wpadminbar #wp-admin-bar-imagify-profile .imagify-btn-ghost:focus,#wpadminbar #wp-admin-bar-imagify-profile .imagify-btn-ghost:hover{background:#FFF;color:#888}#wpadminbar .imagify-warning *{background:#f5a623;color:#FFF;text-shadow:0 0 2px rgba(0,0,0,.2)}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/admin.css b/wp-content/plugins/imagify/assets/css/admin.css
new file mode 100644
index 00000000..e821c8c6
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/admin.css
@@ -0,0 +1,1481 @@
+/**
+ * == Utilities Classes
+ */
+
+/* Util: Flexbox */
+.imagify-flex {
+ display: flex;
+}
+.imagify-vcenter {
+ align-items: center;
+}
+.imagify-noshrink {
+ flex-shrink: 0;
+}
+.imagify-nogrow {
+ flex-grow: 0;
+}
+
+/* Util: dimension */
+.imagify-wauto {
+ width: auto;
+}
+.imagify-hauto {
+ height: auto;
+}
+.imagify-full-width {
+ width: 100%;
+}
+
+/* Util: float */
+.imagify-start {
+ float: left;
+}
+.imagify-end {
+ float: right;
+}
+
+/* Util: text-align */
+.imagify-txt-start.imagify-txt-start.imagify-txt-start {
+ text-align: left;
+}
+.imagify-txt-center.imagify-txt-center.imagify-txt-center {
+ text-align: center;
+}
+.imagify-txt-end.imagify-txt-end.imagify-txt-end {
+ text-align: right;
+}
+
+/* Util: margin/padding */
+.imagify-mt0.imagify-mt0 {
+ margin-top: 0;
+}
+.imagify-mt1.imagify-mt1 {
+ margin-top: 1em;
+}
+.imagify-mt2.imagify-mt2 {
+ margin-top: 2em;
+}
+.imagify-mt3.imagify-mt3 {
+ margin-top: 3em;
+}
+.imagify-mb0.imagify-mb0 {
+ margin-bottom: 0;
+}
+.imagify-mb1.imagify-mb1 {
+ margin-bottom: 1em;
+}
+.imagify-mr1.imagify-mr1 {
+ margin-right: 1em;
+}
+.imagify-ml2.imagify-ml2 {
+ margin-left: 2em;
+}
+.imagify-mr2.imagify-mr2 {
+ margin-right: 2em;
+}
+
+.imagify-pl0.imagify-pl0.imagify-pl0 {
+ padding-left: 0;
+}
+.imagify-pb0.imagify-pb0 {
+ padding-bottom: 0;
+}
+.imagify-pr1.imagify-pr1 {
+ padding-right: 1em;
+}
+.imagify-pr2.imagify-pr2 {
+ padding-right: 2em;
+}
+
+/* Util: Overflow */
+.imagify-oh {
+ overflow: hidden;
+}
+.imagify-clear {
+ clear: both;
+}
+.imagify-clearfix:after,
+.imagify-inline-options:after,
+.imagify-settings-main-content:after,
+.imagify-settings-section:after {
+ content: "";
+ display: table;
+ width: 100%;
+ clear: both;
+}
+.imagify-setting-optim-level .imagify-inline-options:after {
+ display: none;
+}
+
+/* Util: Dividers */
+.imagify-divider {
+ height: 1px;
+ margin: 20px 0;
+ background: #D2D3D6;
+}
+.imagify-pipe {
+ display: inline-block;
+ margin: 0 .75em;
+ vertical-align: middle;
+ height: 15px;
+ width: 1px;
+ background: #979797;
+}
+
+/* Titles */
+.imagify-h3-like.imagify-h3-like.imagify-h3-like {
+ margin-bottom: 0;
+ font-size: 19px;
+ font-weight: 500;
+ color: #1F2332;
+}
+.imagify-h4-like.imagify-h4-like.imagify-h4-like {
+ font-size: 14px;
+ font-weight: bold;
+ color: #2E3243;
+}
+
+/* Default counter */
+.imagify-count.imagify-count {
+ counter-reset: num;
+}
+.imagify-count .imagify-count-title {
+ font-weight: bold;
+}
+.imagify-default-settings {
+ color: #73818c;
+ font-weight: normal;
+}
+.imagify-count .imagify-count-title:before {
+ counter-increment: num 1;
+ content: counter(num) ". ";
+}
+
+/* List counter */
+.imagify-count-list {
+ counter-reset: listcount;
+}
+.imagify-count-list li {
+ display: flex;
+ align-items: center;
+}
+.imagify-count-list li + li {
+ margin-top: .5em;
+}
+.imagify-count-list li:before {
+ display: flex;
+ flex-basis: 24px;
+ flex-shrink: 0;
+ flex-grow: 0;
+ align-items: center;
+ justify-content: center;
+ margin-right: 16px;
+ border: 2px solid #40b1d0;
+ width: 24px;
+ height: 24px;
+ counter-increment: listcount 1;
+ content: counter(listcount);
+ color: #40b1d0;
+ border-radius: 50%;
+}
+
+/* Table layout */
+.imagify-table {
+ display: table;
+ width: 100%;
+}
+.imagify-cell {
+ display: table-cell;
+ padding: 10px;
+ vertical-align: top;
+}
+.imagify-cell.va-top,
+.va-top .imagify-cell {
+ vertical-align: top;
+}
+
+.imagify-bulk-submit .imagify-cell {
+ padding-top: 0;
+}
+
+/* When an "Imagify" modal is open in a page */
+body.imagify-modal-is-open {
+ overflow: hidden;
+}
+
+/* Loader/Spinner */
+.imagify-spinner {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ margin-right: 5px;
+ vertical-align: middle;
+ background: rgba(0, 0, 0, 0) url("../images/spinner.gif") no-repeat scroll 0 0 / 20px 20px;
+ opacity: 0.7;
+}
+.spinner.imagify-hidden {
+ width: 0;
+ margin: 4px 0 0 0;
+}
+
+/* Some basic colors */
+.imagify-primary.imagify-primary.imagify-primary {
+ color: #40b1d0;
+}
+.imagify-secondary.imagify-secondary.imagify-secondary,
+.imagify-valid {
+ color: #8BC34A;
+}
+
+/* Informations in column (media popin, media details) */
+.misc-pub-section.misc-pub-imagify h4 {
+ font-size: 14px;
+ margin-top: 5px;
+ margin-bottom: 0;
+}
+
+/* Doughnut */
+.imagify-chart {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ vertical-align: middle;
+}
+.imagify-chart-container {
+ position: relative;
+ display: inline-block;
+ margin-right: 5px;
+}
+.imagify-chart-container canvas {
+ display: block;
+}
+
+/**
+ *
+ * == Settings page
+ *
+ */
+
+
+/* Basic HTML elements for Options and Bulk pages */
+.imagify-settings a,
+.imagify-settings .button,
+.imagify-settings input,
+.imagify-welcome a,
+.imagify-welcome .button,
+.imagify-weolcome input {
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+.imagify-settings a {
+ color: #40b1d0;
+}
+
+.imagify-settings,
+.imagify-settings p,
+.imagify-settings th {
+ color: #5F758E;
+}
+
+/* Buttons */
+.imagify-settings .button,
+.imagify-welcome .button,
+.imagify-notice .button,
+.imagify-button.imagify-button,
+.imagify-button-primary.imagify-button-primary,
+.imagify-button-secondary.imagify-button-secondary {
+ height: auto;
+ padding: 11px 22px;
+ border: 0 none;
+ font-size: 14px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.01em;
+ word-spacing: 0.01em;
+ box-shadow: 0 3px 0 rgba(0, 0, 0, .15);
+ border-radius: 3px;
+ cursor: pointer;
+ transition: all .275s;
+}
+
+.button-primary.button-mini {
+ padding: 2px 10px;
+}
+.imagify-settings .button.button-mini-flat {
+ padding: 3px 6px 5px;
+ font-size: 12px;
+ box-shadow: none!important;
+ line-height: 1.2;
+}
+.imagify-settings .button.button-mini-flat:hover,
+.imagify-settings .button.button-mini-flat:focus {
+ box-shadow: none!important;
+}
+
+.imagify-title .button-ghost.button-ghost,
+.imagify-button-ghost.imagify-button-ghost {
+ padding: 2px 9px;
+ border: 1px solid #40B1D0;
+ font-size: 12px;
+ font-weight: normal;
+ color: #40B1D0;
+ background: transparent;
+ box-shadow: none;
+}
+.imagify-title .button-ghost.button-ghost:hover,
+.imagify-title .button-ghost.button-ghost:focus,
+.imagify-button-ghost.imagify-button-ghost:hover,
+.imagify-button-ghost.imagify-button-ghost:focus {
+ border-color: transparent;
+ color: #000;
+ background: #40B1D0;
+}
+.imagify-button-ghost.imagify-button-ghost:hover,
+.imagify-button-ghost.imagify-button-ghost:focus {
+ color: #FFF;
+}
+.imagify-button-medium.imagify-button-medium {
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ padding: 3px 10px;
+ font-weight: bold;
+}
+.imagify-button-medium.imagify-button-ghost {
+ border-width: 2px;
+}
+[class*="imagify-"] .button .dashicons {
+ margin-right: 5px;
+ vertical-align: middle;
+}
+
+.imagify-settings .button-primary.button-primary,
+.imagify-welcome .button-primary.button-primary,
+.imagify-button-primary.imagify-button-primary {
+ background: #40B1D0;
+ color: #FFF;
+ box-shadow: 0 3px 0 rgba(51, 142, 166, 1);
+ text-shadow: 0 -1px 1px #006799, 1px 0 1px #006799, 0 1px 1px #006799!important;
+}
+.imagify-button-secondary.imagify-button-secondary {
+ background: #8BC34A;
+ color: #FFF;
+ box-shadow: 0 3px 0 #6F9C3B;
+ text-shadow: 0 -1px 1px #6F9C3B, 1px 0 1px #6F9C3B, 0 1px 1px #6F9C3B!important;
+}
+.imagify-settings .button-primary:hover,
+.imagify-settings .button-primary:focus,
+.imagify-welcome .button-primary:hover,
+.imagify-welcome .button-primary:focus,
+.imagify-button-primary.imagify-button-primary:hover,
+.imagify-button-primary.imagify-button-primary:focus {
+ background: rgb(51, 142, 166);
+ box-shadow: 0 3px 0 rgb(31, 122, 146);
+}
+.imagify-button-secondary.imagify-button-secondary:hover,
+.imagify-button-secondary.imagify-button-secondary:focus {
+ background: #6F9C3B;
+ color: #FFF;
+}
+
+.imagify-button-light.imagify-button-light {
+ background: #FFF;
+ color: #4a4a4a;
+ box-shadow: 0 2px 0 rgba(0, 0, 0, .2);
+}
+.imagify-block-secondary .imagify-button-light.imagify-button-light {
+ color: #6F9C3B;
+}
+.imagify-button-light.imagify-button-light:hover,
+.imagify-button-light.imagify-button-light:focus {
+ color: #FFF;
+ background: rgba(0, 0, 0, .2);
+}
+
+/* Buttons clean */
+.button.imagify-button-clean,
+.imagify-button-clean {
+ padding: 0;
+ background: transparent;
+ box-shadow: none;
+}
+.imagify-button-clean .dashicons-plus {
+ width: 32px;
+ height: 25px;
+}
+.imagify-button-clean .dashicons-plus:before {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 25px;
+ height: 22px;
+ margin-left: 2px;
+ padding-top: 3px;
+ font-size: 17px;
+ background: #40B1D0;
+ color: #FFF;
+ transition: all .275s;
+}
+.button.imagify-button-clean:hover,
+.button.imagify-button-clean:focus,
+.button.imagify-button-clean:active,
+.button.imagify-button-clean[disabled] {
+ background: transparent!important;
+ color: #343A49;
+ box-shadow: none;
+}
+.button.imagify-button-clean:hover .dashicons-plus:before,
+.button.imagify-button-clean:focus .dashicons-plus:before {
+ background: #343A49;
+}
+
+/* Buttons link-like */
+button.imagify-link-like {
+ border: 0;
+ padding: 0;
+ color: inherit;
+ text-decoration: underline;
+ font-size: 13px;
+ box-shadow: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+/* Modifier */
+.imagify-section-positive .imagify-button-light {
+ color: #709A41;
+}
+.imagify-button.imagify-button-big {
+ font-size: 15px;
+ padding: 11px 30px;
+}
+.imagify-button-big .dashicons {
+ font-size: 1.45em;
+ margin-right: 6px;
+ margin-left: -4px;
+}
+
+.imagify-settings .button .dashicons,
+.imagify-welcome .button .dashicons,
+.imagify-notice .button .dashicons,
+.imagify-button.imagify-button .dashicons,
+.imagify-button-primary.imagify-button-primary .dashicons,
+.imagify-button-secondary.imagify-button-secondary .dashicons {
+ vertical-align: middle;
+}
+
+[class*="imagify-"] .button-text {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+/* Exception in Media edition page and post Edition pages (insert media popin) */
+.wp_attachment_image .imagify-button-primary,
+.media-frame-content .imagify-button-primary {
+ padding: 0 10px 1px;
+ margin: 0 5px 2px 0;
+ font-size: 13px;
+ line-height: 26px;
+ box-shadow: 0 3px 0 rgba(51, 142, 166, 1);
+}
+.wp_attachment_image .imagify-button-primary {
+ float: left;
+}
+
+/**
+ * == Header & Subheader & Sections
+ *
+ * (options, Welcome Notice, Bulk)
+ */
+.imagify-title.imagify-title {
+ position: relative;
+ padding: 10px 30px;
+ font-size: 23px;
+ background: #1F2332;
+ color: #FFF;
+}
+.imagify-welcome .imagify-logo {
+ opacity: 1;
+}
+.imagify-welcome .imagify-title {
+ display: flex;
+ padding: 20px 30px;
+}
+.imagify-settings .imagify-title { /* (options and bulk) */
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.imagify-settings .imagify-logo-block {
+ display: flex;
+ align-items: center;
+ flex-shrink: 0;
+ flex-grow: 0;
+ padding: 0;
+ margin-right: 35px;
+ color: inherit;
+}
+.imagify-logo-block sup {
+ color: #1F2332;
+}
+.imagify-settings .imagify-title + .imagify-notice {
+ margin: 0;
+ border-right: 1px solid #D9D9D9;
+ padding-top: 15px;
+ padding-bottom: 15px;
+}
+.imagify-title .title-text {
+ font-size: 28px;
+ font-weight: bold;
+ color: #FFF;
+}
+.imagify-lb-icon {
+ padding-right: 18px;
+}
+.imagify-lb-text img {
+ margin-bottom: .15em;
+}
+.imagify-lb-text {
+ font-size: 23px;
+ font-weight: bold;
+ color: #FFF;
+}
+.imagify-logo {
+ display: block;
+ vertical-align: top;
+ opacity: .4;
+}
+.imagify-sub-header,
+.imagify-sub-title.imagify-sub-title, /* heavier is better */
+.imagify-settings div.submit,
+.imagify-section {
+ margin: 0;
+ padding: 20px;
+ background: #F2F5F7;
+}
+.imagify-sub-title.imagify-sub-title,
+.imagify-section-positive {
+ padding-left: 40px;
+}
+.imagify-section-positive {
+ background: #8cc152;
+ color: #FFF;
+}
+.imagify-section-positive p {
+ color: #FFF
+}
+.imagify-section-gray {
+ background: #D9E4EB;
+}
+.imagify-section-gray .imagify-count-title {
+ color: #4a4a4a;
+}
+.imagify-section p:first-child {
+ margin-top: 0;
+}
+.imagify-section p:last-child {
+ margin-bottom: 0;
+}
+
+/* Documentation link */
+.imagify-settings .imagify-documentation-link-box {
+ display: flex;
+ padding: 12px 13px 14px;
+ border: 1px solid #40b1d0;
+ color: #E5EBEF;
+ border-radius: 3px;
+}
+.imagify-documentation-link-icon {
+ width: 23px;
+ height: 31px;
+ font-size: 2.6em;
+ margin-right: 15px;
+ line-height: 1.3;
+}
+.imagify-documentation-link-box span {
+ font-size: 12px;
+}
+.imagify-documentation-link-box a {
+ font-weight: bold;
+}
+
+@media (max-width: 1120px) {
+ .imagify-settings .imagify-title {
+ flex-wrap: wrap;
+ }
+}
+
+.imagify-settings-section {
+ padding: 10px 20px;
+}
+.imagify-account-info-col .imagify-settings-section {
+ padding-right: 0;
+}
+.imagify-settings-main-content,
+.imagify-welcome .imagify-settings-section {
+ border: 1px solid #D9D9D9;
+ border-top-width: 0;
+ background: #FFF;
+}
+
+.imagify-settings-main-content {
+ padding-bottom: 20px;
+}
+.imagify-settings-main-content p,
+.imagify-settings-main-content .imagify-setting-line {
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+.imagify-settings-main-content .code {
+ max-height: 10em;
+ padding: 3px 5px 2px 5px;
+ overflow: auto;
+ background: #EAEAEA;
+ background: rgba(0,0,0,.07);
+}
+
+.imagify-settings-main-content + .imagify-settings-main-content {
+ margin-top: 20px;
+ border-top-width: 1px;
+}
+
+.imagify-br {
+ line-height: 2;
+}
+
+/* New to imagify, title */
+p.imagify-section-title.imagify-section-title {
+ font-size: 20px;
+ margin-top: -.3em;
+ margin-bottom: -.6em;
+}
+
+/**
+ * == Rating (Notice + Settings)
+ */
+.imagify-rate-us.imagify-rate-us {
+ text-align: right;
+ margin: -1em -2.4em -1em 0;
+ color: #FFF;
+}
+.imagify-rate-us a {
+ color: #40B1D0;
+}
+.imagify-rate-us .stars {
+ display: inline-block;
+ margin: 2px 0 0 10px;
+ text-decoration: none;
+ letter-spacing: .2em;
+ vertical-align: -1px;
+}
+.imagify-rate-us .stars .dashicons:before {
+ font-size: 18px;
+}
+.imagify-rate-us a:hover,
+.imagify-rate-us a:focus {
+ color: #FEE102;
+}
+@media (max-width: 1220px) {
+ .imagify-rate-us.imagify-rate-us {
+ position: static;
+ margin-bottom: 0;
+ text-align: left;
+ }
+ .imagify-rate-us.imagify-rate-us br {
+ display: none;
+ }
+ .imagify-rate-us .stars {
+ display: block;
+ margin-left: 0;
+ }
+}
+
+/**
+ * == Messages & infos
+ */
+.imagify-important {
+ color: #F5A623;
+}
+.imagify-success,
+.imagify-settings .imagify-success {
+ color: #8BC34A;
+}
+.imagify-info,
+.imagify-info a {
+ color: #7A8996;
+ font-size: 12px;
+}
+.imagify-info {
+ position: relative;
+ display: inline-block;
+ padding-left: 25px;
+}
+.imagify-info .dashicons {
+ position: absolute;
+ left: 0; top: 0;
+ color: #40B1D0;
+}
+
+/* Custom checkboxes in CSS */
+.imagify-settings.imagify-settings [type="checkbox"]:not(:checked),
+.imagify-settings.imagify-settings [type="checkbox"]:checked,
+.imagify-checkbox.imagify-checkbox:not(:checked),
+.imagify-checkbox.imagify-checkbox:checked {
+ position: absolute;
+ opacity: 0.01;
+}
+.imagify-settings.imagify-settings [type="checkbox"]:not(:checked):focus,
+.imagify-settings.imagify-settings [type="checkbox"]:checked:focus,
+.imagify-checkbox.imagify-checkbox:not(:checked):focus,
+.imagify-checkbox.imagify-checkbox:checked:focus {
+ box-shadow: none!important; /* system value to override */
+ outline: none!important;
+ border: 0 none!important;
+}
+
+.imagify-settings [type="checkbox"]:not(:checked) + label,
+.imagify-settings [type="checkbox"]:checked + label,
+.imagify-checkbox.imagify-checkbox:not(:checked) + label,
+.imagify-checkbox.imagify-checkbox:checked + label {
+ position: relative;
+ display: flex;
+ align-items: center;
+ min-height: 24px;
+ padding-left: 40px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: bold;
+ color: #2E3243;
+}
+
+/* checkbox aspect */
+.imagify-settings [type="checkbox"]:not(:checked) + label:before,
+.imagify-settings [type="checkbox"]:checked + label:before,
+.imagify-checkbox.imagify-checkbox:not(:checked) + label:before,
+.imagify-checkbox.imagify-checkbox:checked + label:before {
+ content: '';
+ position: absolute;
+ left: 0; top: 0;
+ width: 22px; height: 22px;
+ border: 2px solid #8BA6B4;
+ background: #FFFFFF;
+ border-radius: 3px;
+}
+/* checked mark aspect */
+.imagify-settings [type="checkbox"]:not(:checked) + label:after,
+.imagify-settings [type="checkbox"]:checked + label:after,
+.imagify-checkbox.imagify-checkbox:not(:checked) + label:after,
+.imagify-checkbox.imagify-checkbox:checked + label:after {
+ content: "â";
+ position: absolute;
+ font-size: 1.4em;
+ top: -2px; left: 4.5px;
+ color: #8BA6B4;
+ font-weight: normal;
+ -webkit-transition: all .2s;
+ -moz-transition: all .2s;
+ -ms-transition: all .2s;
+ transition: all .2s;
+}
+/* disabled aspect */
+.imagify-settings [type="checkbox"][disabled]:not(:checked) + label:before,
+.imagify-settings [type="checkbox"][disabled]:checked + label:before,
+.imagify-checkbox.imagify-checkbox[disabled]:not(:checked) + label:before,
+.imagify-checkbox.imagify-checkbox[disabled]:checked + label:before {
+ border-color: #ccc;
+ background: #ddd;
+}
+/* checked mark aspect changes */
+.imagify-settings [type="checkbox"]:not(:checked) + label:after,
+.imagify-checkbox.imagify-checkbox:not(:checked) + label:after {
+ opacity: 0;
+ -webkit-transform: scale(0);
+ -moz-transform: scale(0);
+ -ms-transform: scale(0);
+ transform: scale(0);
+}
+.imagify-settings [type="checkbox"]:checked + label:after,
+.imagify-checkbox.imagify-checkbox:checked + label:after {
+ opacity: 1;
+ -webkit-transform: scale(1);
+ -moz-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+}
+
+/* medium version */
+.medium.imagify-checkbox:not(:checked) + label:before,
+.medium.imagify-checkbox:checked + label:before {
+ width: 22px;
+ height: 22px;
+ border-width: 1.5px;
+ border-radius: 2px;
+ margin-top: 0;
+}
+.medium.imagify-checkbox:not(:checked) + label:after,
+.medium.imagify-checkbox:checked + label:after {
+ font-size: 1.1em;
+ left: -17px;
+ top: 3px;
+}
+
+/* mini version */
+.imagify-settings .mini[type="checkbox"]:not(:checked) + label:before,
+.imagify-settings .mini[type="checkbox"]:checked + label:before,
+.mini.imagify-checkbox:not(:checked) + label:before,
+.mini.imagify-checkbox:checked + label:before {
+ width: 15px;
+ height: 15px;
+ border-width: 1px;
+ border-radius: 2px;
+ margin-top: 0;
+}
+.imagify-settings .mini[type="checkbox"]:not(:checked) + label:after,
+.imagify-settings .mini[type="checkbox"]:checked + label:after,
+.mini.imagify-checkbox:not(:checked) + label:after,
+.mini.imagify-checkbox:checked + label:after {
+ font-size: .9em;
+ left: -21px;
+ top: -0.5px;
+}
+/* focus aspect */
+.imagify-settings [type="checkbox"]:not(:checked):focus + label:before,
+.imagify-settings [type="checkbox"]:checked:focus + label:before,
+.imagify-checkbox.imagify-checkbox:not(:checked):focus + label:before,
+.imagify-checkbox.imagify-checkbox:checked:focus + label:before {
+ border-style: dotted;
+ border-color: #40b1d0;
+}
+
+/* Checkbox groups */
+.imagify-check-group {
+ padding-left: 2px;
+ margin-bottom: 0;
+}
+.imagify-check-group.imagify-is-scrollable {
+ height: 15em;
+ overflow-y: auto;
+ padding: 8px;
+ margin: 1.5em 0 0 -8px;
+ background: #F4F7F9;
+ border: 1px solid #D2D3D6;
+ border-radius: 3px;
+}
+.imagify-is-scrollable legend + p {
+ margin-top: 0;
+}
+.imagify-is-scrollable [type="checkbox"]:not(:checked) + label:before,
+.imagify-is-scrollable [type="checkbox"]:checked + label:before {
+ background: #F4F7F9;
+}
+.imagify-settings .imagify-check-group.imagify-check-group label {
+ color: #338EA6;
+ font-weight: 500;
+}
+
+/* Custom radio in CSS */
+.imagify-inline-options {
+ position: relative;
+ display: table;
+ width: 100%;
+ max-width: 600px;
+ border-collapse: collapse;
+}
+
+.imagify-inline-options input[type="radio"]:not(:checked),
+.imagify-inline-options input[type="radio"]:checked {
+ position: absolute;
+ left: 5px; top: 5px;
+ display: none;
+}
+
+.imagify-inline-options input[type="radio"]:not(:checked) + label,
+.imagify-inline-options input[type="radio"]:checked + label {
+ position: relative;
+ display: table-cell;
+ padding: 13px 10px;
+ text-align: center;
+
+ font-weight: 600;
+ font-size: 16px;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: #FFF;
+ background: #2E3243;
+ box-shadow: 0 -3px 0 rgba(0, 0, 0, 0.1) inset;
+ z-index: 2;
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+
+.imagify-inline-options input[type="radio"]:not(:checked) + label:first-of-type,
+.imagify-inline-options input[type="radio"]:checked + label:first-of-type {
+ border-radius: 3px 0 0 3px;
+}
+
+.imagify-inline-options input[type="radio"]:not(:checked) + label:last-of-type,
+.imagify-inline-options input[type="radio"]:checked + label:last-of-type {
+ border-radius: 0 3px 3px 0;
+}
+
+.imagify-inline-options input[type="radio"]:checked + label {
+ background: #8BC34A
+}
+
+.imagify-inline-options .imagify-info {
+ margin-top: 15px;
+}
+
+/**
+ * Account information columns (Settings & Bulk)
+ */
+.imagify-col.imagify-col.imagify-account-info-col,
+.imagify-account-info-col {
+ width: 380px;
+ max-width: 100%;
+ padding: 0 20px 0 0;
+}
+.imagify-col.imagify-col.imagify-shared-with-account-col,
+.imagify-shared-with-account-col {
+ width: calc(100% - 380px);
+ padding: 0;
+}
+
+.imagify-account-info-col .imagify-options-title {
+ padding: 24px 26px;
+ color: #FFF;
+ background: #1F2332;
+}
+.imagify-block-secondary {
+ padding: 26px 26px 35px;
+ border: 1px solid #75A345;
+ background: #8BC34A;
+ border-radius: 3px;
+ color: #FFF;
+}
+.imagify-block-secondary.imagify-block-secondary p,
+.imagify-account-info-col .imagify-block-secondary.imagify-block-secondary h3 {
+ color: inherit;
+}
+.imagify-account-info-col .imagify-col-content h3:first-child {
+ margin-top: 0;
+}
+.imagify-account-info-col .imagify-col-content h3 {
+ font-size: 19px;
+}
+.imagify-account-info-col .imagify-col-content p {
+ margin: 1.5em 0;
+}
+.imagify-account-info-col .imagify-col-content p:first-child {
+ margin-top: 0;
+}
+
+.imagify-user-plan-label {
+ float: right;
+ margin-top: -4px;
+ padding: 2px 10px;
+ border: 2px solid #40B1D0;
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 0.02em;
+ color: #40B1D0;
+ border-radius: 3px;
+}
+
+/* Content given after remote answer, avoid "flash" effect on content */
+.imagify-user-plan-label:empty {
+ display: none;
+}
+
+/**
+ * == Columns
+ */
+.imagify-columns {
+ overflow: hidden;
+ padding: 15px 0;
+ counter-reset: cols;
+}
+.imagify-columns [class^="col-"] {
+ float: left;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.imagify-columns .col-1-3 {
+ width: 33.333%;
+ padding-left: 28px;
+}
+.imagify-columns .col-2-3 {
+ width: 66.666%;
+ padding-left: 28px
+}
+.imagify-columns .col-1-2 {
+ width: 50%;
+ padding: 0 20px;
+}
+
+@media (max-width: 830px) {
+ .imagify-columns [class^="col-"] {
+ float: none;
+ margin-bottom: 1.5em;
+ }
+ .imagify-columns .col-1-3,
+ .imagify-columns .col-1-2 {
+ width: auto;
+ padding: 0 28px;
+ clear: both;
+ padding-top: 1em;
+ }
+}
+
+/**
+ * == Custom column & Metabox
+ */
+.column-imagify_optimized_file.column-imagify_optimized_file {
+ width: 300px;
+ text-align: center;
+ vertical-align: middle;
+}
+.column-imagify_optimized_file > * {
+ max-width: 21em;
+ margin: 0 auto;
+}
+@media (min-width: 1151px) and (max-width: 1800px) {
+ .column-imagify_optimized_file.column-imagify_optimized_file {
+ width: 21em;
+ }
+}
+@media (min-width: 783px) and (max-width: 1150px) {
+ .column-imagify_optimized_file.column-imagify_optimized_file {
+ width: 13em;
+ }
+ table.media .column-title .has-media-icon ~ .row-actions.row-actions {
+ margin-left: 0;
+ }
+}
+@media (max-width: 782px) {
+ table.media .column-imagify_optimized_file.column-imagify_optimized_file {
+ text-align: left;
+ }
+ table.media .imagify-datas-more-action,
+ table.media .imagify-datas-actions-links {
+ text-align: center;
+ }
+ table.media .column-imagify_optimized_file > *,
+ table.media .column-imagify_optimized_file .imagify-datas-actions-links a {
+ max-width: 100%;
+ margin-left: 0;
+ }
+}
+@media (min-width: 783px) and (max-width: 1150px), (max-width: 360px) {
+ table.media .imagify-hide-if-small {
+ position: absolute;
+ margin: -1px;
+ padding: 0;
+ height: 1px;
+ width: 1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ border: 0;
+ word-wrap: normal !important; /* Many screen reader and browser combinations announce broken words as they would appear visually. */
+ }
+}
+.compat-field-imagify .label {
+ vertical-align: top;
+}
+.compat-field-imagify ul.imagify-datas-list {
+ margin-top: 7px;
+ font-size: 11px;
+}
+ul.imagify-datas-list.imagify-datas-list {
+ margin: 0 auto;
+ color: #555;
+}
+ul.imagify-datas-list .big {
+ font-size: 12px;
+ color: #40B1D0;
+}
+.imagify-data-item {
+ overflow: hidden;
+}
+li.imagify-data-item {
+ clear: both;
+ margin-bottom: 2px;
+}
+ul.imagify-datas-list .imagify-data-item span.data,
+ul.imagify-datas-list .imagify-data-item strong {
+ float: left;
+ width: 38%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+ul.imagify-datas-list .imagify-data-item span.data {
+ width: 62%;
+ padding-right: 5px;
+ text-align: left;
+}
+.compat-field-imagify .imagify-datas-list .imagify-data-item .data {
+ width: 130px;
+ text-align: left;
+ font-weight: bold;
+}
+ul.imagify-datas-list .imagify-data-item strong {
+ text-align: left;
+ padding-left: 5px;
+}
+.media-sidebar .imagify-datas-list .imagify-data-item .data {
+ width: auto;
+ float: none;
+}
+.media-sidebar .imagify-datas-list .imagify-data-item strong {
+ display: inline-block;
+ width: auto;
+ float: none;
+}
+.media-sidebar .imagify-datas-list .imagify-data-item .imagify-chart {
+ float: left;
+}
+.imagify-datas-more-action.imagify-datas-more-action {
+ margin: .4em auto;
+ background: linear-gradient(to bottom, transparent, transparent 49%, rgba(0,0,0,.075) 50%, rgba(0,0,0,.075) 58%, transparent 58%, transparent);
+}
+.imagify-datas-more-action a {
+ display: inline-block;
+ padding: 0 5px;
+ background: #40B1D0;
+ color: #FFF;
+ text-transform: uppercase;
+ font-size: 9px;
+ font-weight: bold;
+ line-height: 1.9;
+ text-decoration: none;
+}
+.imagify-datas-more-action a.is-open {
+ background: #555;
+}
+.imagify-datas-more-action a.is-open .dashicons {
+ transform: rotate(180deg);
+}
+.imagify-datas-more-action a .dashicons {
+ font-size: 14px;
+ vertical-align: middle;
+ line-height: .8;
+}
+.imagify-datas-more-action a .dashicons:before {
+ vertical-align: middle;
+ line-height: 20px;
+}
+.imagify-datas-more-action .the-text {
+ display: inline-block;
+ vertical-align: middle;
+ height: auto;
+ line-height: inherit;
+}
+
+ul.imagify-datas-details.imagify-datas-details {
+ margin: .7em auto;
+}
+.imagify-datas-details strong {
+ color: #40B1D0;
+}
+.imagify-datas-details .original {
+ color: #555;
+}
+
+.imagify-datas-actions-links {
+ overflow: hidden;
+ border-top: 2px solid transparent;
+ padding-top: 5px;
+ font-size: 11px;
+}
+.nggform .imagify-datas-actions-links {
+ position: relative;
+ z-index: 2;
+}
+.nggform .row-actions {
+ z-index: 1;
+}
+.imagify-datas-actions-links a {
+ position: relative;
+ display: inline-block;
+ padding-left: 17px;
+ text-decoration: none;
+ font-weight: 600;
+}
+.compat-field-imagify .imagify-datas-actions-links {
+ max-width: 300px;
+}
+.misc-pub-imagify .imagify-datas-actions-links {
+ border-top: 2px solid #f2f2f2;
+ padding-bottom: 5px;
+}
+/* Library */
+.column-imagify_optimized_file .imagify-datas-actions-links a {
+ margin: 0 .7em;
+ padding-left: 15px;
+}
+
+/* Media edition */
+.compat-field-imagify .imagify-datas-actions-links a,
+.misc-pub-imagify .imagify-datas-actions-links a {
+ float: left;
+ width: 50%;
+}
+.media-sidebar .compat-field-imagify .imagify-datas-actions-links a,
+.submitbox .misc-pub-imagify .imagify-datas-actions-links a {
+ display: block;
+ width: auto;
+ float: none;
+}
+.media-sidebar .compat-field-imagify .imagify-datas-actions-links br,
+.submitbox .misc-pub-imagify .imagify-datas-actions-links br {
+ display: none;
+}
+.imagify-datas-actions-links a:only-child {
+ float: none;
+ width: auto;
+}
+.imagify-datas-details.is-open + .imagify-datas-actions-links {
+ border-top-color: rgba(0,0,0,.075);
+}
+.imagify-datas-actions-links .dashicons {
+ position: absolute;
+ left: 0; top: 4px;
+ width: 12px;
+ margin-right: 2px;
+ font-size: 11px;
+}
+
+/**
+ * == Bulk page
+ */
+
+.imagify-account,
+.imagify-account-link {
+ padding-right: 15px;
+}
+.imagify-meteo-icon {
+ display: inline-block;
+ height: 38px;
+ vertical-align: middle;
+ margin-right: 10px;
+}
+.imagify-user-plan {
+ color: #40b1d0;
+}
+
+.imagify-meteo-title.imagify-meteo-title {
+ color: #FFF;
+ font-size: 17px;
+}
+.imagify-space-left > p {
+ color: #FFF;
+}
+[class^="imagify-bar-"] {
+ position: relative;
+ height: 8px;
+ width: 100%;
+ background: #60758D;
+ color: #FFF;
+ font-size: 10px;
+}
+
+.imagify-progress {
+ height: 8px;
+}
+.imagify-progress {
+ transition: width .3s;
+}
+.imagify-bar-positive .imagify-progress {
+ background: #8CC152;
+}
+.imagify-bar-positive .imagify-barnb {
+ color: #8CC152;
+}
+.imagify-bar-primary .imagify-progress {
+ background: #40B1D0;
+}
+.imagify-bar-primary .imagify-barnb {
+ color: #40B1D0;
+}
+.imagify-bar-negative .imagify-progress {
+ background: #D2D3D6;
+}
+.imagify-bar-negative .imagify-barnb {
+ color: #7A8996;
+}
+.imagify-bar-neutral .imagify-progress {
+ background: #F5A623;
+}
+.imagify-space-left .imagify-bar-negative .imagify-progress {
+ background: #C51162;
+}
+
+.imagify-btn-ghost {
+ display: inline-block;
+ height: auto;
+ padding: 7px 10px;
+ border: 1px solid #FFF;
+ text-align: center;
+ background: transparent;
+ color: #FFF;
+ border-radius: 3px;
+ transition: all .275s;
+}
+
+.imagify-btn-ghost:hover,
+.imagify-btn-ghost:focus {
+ background: #FFF;
+ color: #888;
+}
+
+/* Some Errors */
+.imagify-error {
+ background: #D0021B;
+ color: #FFF;
+}
+.imagify-settings-section .imagify-error {
+ display: inline-block;
+ padding: 7px 10px;
+ margin: 10px 0 0 45px;
+ border-radius: 3px;
+}
+.imagify-settings-section .imagify-error code {
+ font-weight: normal;
+}
+.imagify-settings-section .imagify-error.hidden {
+ display: none;
+}
+.imagify-warning {
+ background: #f5a623;
+ color: #FFF;
+ text-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
+}
+
+/* Imagify Modal (is everywhere) */
+.imagify-modal {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+}
+.js .imagify-modal {
+ display: none;
+ position: fixed;
+ top: 0; right: 0; bottom: 0; left: 0;
+ background-color: #1F2332;
+ background-color: rgba(31,35,50,.95);
+ z-index: 99999;
+}
+.imagify-modal-content {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ position: relative;
+ width: 800px;
+ max-width: 95%;
+ max-height: 90vw;
+ overflow: auto;
+ padding: 20px 25px;
+ margin: 1em auto;
+ background: #FFF;
+ box-shadow: 1px 1px 4px rgba(0,0,0,.7);
+ border-radius: 3px;
+}
+#imagify-visual-comparison .imagify-modal-content,
+.imagify-visual-comparison .imagify-modal-content {
+ max-width: 1400px;
+ background: transparent;
+ padding: 5px;
+ box-shadow: none;
+ border-radius: 0;
+}
+.imagify-modal .h2 {
+ margin: .5em 0;
+ color: #8ba6b4;
+ font-weight: normal;
+ font-size: 24px;
+ letter-spacing: 0.075em;
+ text-align: center;
+}
+.imagify-modal .h3 {
+ color: #40b1d0;
+ font-weight: normal;
+ font-size: 18px;
+ letter-spacing: 0.075em;
+ text-align: center;
+}
+.imagify-modal .close-btn {
+ display: none;
+ visibility: hidden;
+ position: absolute;
+ right: 20px; top: 20px;
+ font-size: 1.2em;
+ border: 0;
+ background: transparent none;
+ border-radius: 0;
+ cursor: pointer;
+}
+.imagify-modal .close-btn i {
+ margin-left: -2px;
+}
+.imagify-modal .close-btn:hover,
+.imagify-modal .close-btn:focus {
+ color: #40b1d0;
+}
+.js .imagify-modal .close-btn {
+ display: block;
+ visibility: visible;
+}
+
+/* Attachments specifics */
+.wp_attachment_image #imagify-visual-comparison .close-btn,
+.imagify-visual-comparison .close-btn {
+ top: 0;
+}
+
+.wp_attachment_image #imagify-visual-comparison .imagify-modal-content,
+.imagify-visual-comparison .imagify-modal-content {
+ padding-top: 40px;
+}
+
+/* Col, behavior depending on parent */
+.imagify-col {
+ float: left;
+ width: 50%;
+ box-sizing: border-box;
+ -webkit-flex-basis: 50%;
+ -ms-flex-preferred-size: 50%;
+ flex-basis: 50%;
+}
+.imagify-col {
+ padding-right: 20px;
+}
+.imagify-col + .imagify-col {
+ padding-right: 0;
+ padding-left: 20px;
+}
+
+.imagify-col:target {
+ animation: hello 1s 3 linear backwards;
+}
+
+@keyframes hello {
+ 0%, 100% {
+ background: #FFF;
+ }
+ 50% {
+ background: #F4F7F9;
+ }
+}
diff --git a/wp-content/plugins/imagify/assets/css/admin.min.css b/wp-content/plugins/imagify/assets/css/admin.min.css
new file mode 100644
index 00000000..27942a5c
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/admin.min.css
@@ -0,0 +1 @@
+.imagify-count-list li,.imagify-flex{display:-webkit-box;display:-ms-flexbox}.imagify-start,.wp_attachment_image .imagify-button-primary{float:left}.imagify-mt0.imagify-mt0,.imagify-section p:first-child{margin-top:0}.imagify-columns,.imagify-data-item,.imagify-datas-actions-links,.imagify-oh,body.imagify-modal-is-open{overflow:hidden}.imagify-flex{display:flex}.imagify-vcenter{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.imagify-noshrink{-ms-flex-negative:0;flex-shrink:0}.imagify-nogrow{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.imagify-wauto{width:auto}.imagify-hauto{height:auto}.imagify-full-width{width:100%}.imagify-end{float:right}.imagify-txt-start.imagify-txt-start.imagify-txt-start{text-align:left}.imagify-txt-center.imagify-txt-center.imagify-txt-center{text-align:center}.imagify-txt-end.imagify-txt-end.imagify-txt-end{text-align:right}.imagify-mt1.imagify-mt1{margin-top:1em}.imagify-mt2.imagify-mt2{margin-top:2em}.imagify-mt3.imagify-mt3{margin-top:3em}.imagify-mb0.imagify-mb0{margin-bottom:0}.imagify-mb1.imagify-mb1{margin-bottom:1em}.imagify-mr1.imagify-mr1{margin-right:1em}.imagify-ml2.imagify-ml2{margin-left:2em}.imagify-mr2.imagify-mr2{margin-right:2em}.imagify-pl0.imagify-pl0.imagify-pl0{padding-left:0}.imagify-pb0.imagify-pb0{padding-bottom:0}.imagify-pr1.imagify-pr1{padding-right:1em}.imagify-pr2.imagify-pr2{padding-right:2em}.imagify-clear{clear:both}.imagify-clearfix:after,.imagify-inline-options:after,.imagify-settings-main-content:after,.imagify-settings-section:after{content:"";display:table;width:100%;clear:both}.imagify-setting-optim-level .imagify-inline-options:after{display:none}.imagify-divider{height:1px;margin:20px 0;background:#D2D3D6}.imagify-pipe{display:inline-block;margin:0 .75em;vertical-align:middle;height:15px;width:1px;background:#979797}.imagify-cell,.imagify-cell.va-top,.imagify-logo,.va-top .imagify-cell{vertical-align:top}.imagify-h3-like.imagify-h3-like.imagify-h3-like{margin-bottom:0;font-size:19px;font-weight:500;color:#1F2332}.imagify-h4-like.imagify-h4-like.imagify-h4-like{font-size:14px;font-weight:700;color:#2E3243}.imagify-count.imagify-count{counter-reset:num}.imagify-count .imagify-count-title{font-weight:700}.imagify-default-settings{color:#73818c;font-weight:400}.imagify-count .imagify-count-title:before{counter-increment:num 1;content:counter(num) ". "}.imagify-count-list{counter-reset:listcount}.imagify-count-list li{display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.imagify-count-list li+li{margin-top:.5em}.imagify-count-list li:before{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-preferred-size:24px;flex-basis:24px;-ms-flex-negative:0;flex-shrink:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-right:16px;border:2px solid #40b1d0;width:24px;height:24px;counter-increment:listcount 1;content:counter(listcount);color:#40b1d0;border-radius:50%}.imagify-table{display:table;width:100%}.imagify-cell{display:table-cell;padding:10px}.imagify-bulk-submit .imagify-cell{padding-top:0}.imagify-spinner{display:inline-block;width:20px;height:20px;margin-right:5px;vertical-align:middle;background:url(../images/spinner.gif) 0 0/20px 20px no-repeat rgba(0,0,0,0);opacity:.7}.spinner.imagify-hidden{width:0;margin:4px 0 0}.imagify-primary.imagify-primary.imagify-primary{color:#40b1d0}.imagify-secondary.imagify-secondary.imagify-secondary,.imagify-valid{color:#8BC34A}.misc-pub-section.misc-pub-imagify h4{font-size:14px;margin-top:5px;margin-bottom:0}.imagify-chart{position:relative;top:1px;display:inline-block;vertical-align:middle}.imagify-chart-container{position:relative;display:inline-block;margin-right:5px}.imagify-chart-container canvas{display:block}.imagify-settings .button,.imagify-settings a,.imagify-settings input,.imagify-welcome .button,.imagify-welcome a,.imagify-weolcome input{-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-settings a{color:#40b1d0}.imagify-settings,.imagify-settings p,.imagify-settings th{color:#5F758E}.imagify-button-primary.imagify-button-primary,.imagify-button-secondary.imagify-button-secondary,.imagify-button.imagify-button,.imagify-notice .button,.imagify-settings .button,.imagify-welcome .button{height:auto;padding:11px 22px;border:0;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.01em;word-spacing:.01em;-webkit-box-shadow:0 3px 0 rgba(0,0,0,.15);box-shadow:0 3px 0 rgba(0,0,0,.15);border-radius:3px;cursor:pointer;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.button-primary.button-mini{padding:2px 10px}.imagify-settings .button.button-mini-flat{padding:3px 6px 5px;font-size:12px;-webkit-box-shadow:none!important;box-shadow:none!important;line-height:1.2}.imagify-settings .button.button-mini-flat:focus,.imagify-settings .button.button-mini-flat:hover{-webkit-box-shadow:none!important;box-shadow:none!important}.imagify-button-ghost.imagify-button-ghost,.imagify-title .button-ghost.button-ghost{padding:2px 9px;border:1px solid #40B1D0;font-size:12px;font-weight:400;color:#40B1D0;background:0 0;-webkit-box-shadow:none;box-shadow:none}.imagify-button-ghost.imagify-button-ghost:focus,.imagify-button-ghost.imagify-button-ghost:hover,.imagify-title .button-ghost.button-ghost:focus,.imagify-title .button-ghost.button-ghost:hover{border-color:transparent;color:#000;background:#40B1D0}.imagify-button-ghost.imagify-button-ghost:focus,.imagify-button-ghost.imagify-button-ghost:hover{color:#FFF}.imagify-button-medium.imagify-button-medium{text-transform:uppercase;letter-spacing:.1em;padding:3px 10px;font-weight:700}.imagify-button-medium.imagify-button-ghost{border-width:2px}[class*=imagify-] .button .dashicons{margin-right:5px;vertical-align:middle}.imagify-button-primary.imagify-button-primary,.imagify-settings .button-primary.button-primary,.imagify-welcome .button-primary.button-primary{background:#40B1D0;color:#FFF;-webkit-box-shadow:0 3px 0 rgba(51,142,166,1);box-shadow:0 3px 0 rgba(51,142,166,1);text-shadow:0 -1px 1px #006799,1px 0 1px #006799,0 1px 1px #006799!important}.imagify-button-secondary.imagify-button-secondary{background:#8BC34A;color:#FFF;-webkit-box-shadow:0 3px 0 #6F9C3B;box-shadow:0 3px 0 #6F9C3B;text-shadow:0 -1px 1px #6F9C3B,1px 0 1px #6F9C3B,0 1px 1px #6F9C3B!important}.imagify-button-primary.imagify-button-primary:focus,.imagify-button-primary.imagify-button-primary:hover,.imagify-settings .button-primary:focus,.imagify-settings .button-primary:hover,.imagify-welcome .button-primary:focus,.imagify-welcome .button-primary:hover{background:#338ea6;-webkit-box-shadow:0 3px 0 #1f7a92;box-shadow:0 3px 0 #1f7a92}.imagify-button-secondary.imagify-button-secondary:focus,.imagify-button-secondary.imagify-button-secondary:hover{background:#6F9C3B;color:#FFF}.imagify-button-light.imagify-button-light{background:#FFF;color:#4a4a4a;-webkit-box-shadow:0 2px 0 rgba(0,0,0,.2);box-shadow:0 2px 0 rgba(0,0,0,.2)}.imagify-block-secondary .imagify-button-light.imagify-button-light{color:#6F9C3B}.imagify-button-light.imagify-button-light:focus,.imagify-button-light.imagify-button-light:hover{color:#FFF;background:rgba(0,0,0,.2)}.button.imagify-button-clean,.imagify-button-clean{padding:0;background:0 0;-webkit-box-shadow:none;box-shadow:none}.imagify-button-clean .dashicons-plus{width:32px;height:25px}.imagify-button-clean .dashicons-plus:before{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:25px;height:22px;margin-left:2px;padding-top:3px;font-size:17px;background:#40B1D0;color:#FFF;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.button.imagify-button-clean:active,.button.imagify-button-clean:focus,.button.imagify-button-clean:hover,.button.imagify-button-clean[disabled]{background:0 0!important;color:#343A49;-webkit-box-shadow:none;box-shadow:none}.button.imagify-button-clean:focus .dashicons-plus:before,.button.imagify-button-clean:hover .dashicons-plus:before{background:#343A49}button.imagify-link-like{border:0;padding:0;color:inherit;text-decoration:underline;font-size:13px;-webkit-box-shadow:none;box-shadow:none;background:0 0;cursor:pointer}.imagify-section-positive .imagify-button-light{color:#709A41}.imagify-button.imagify-button-big{font-size:15px;padding:11px 30px}.imagify-button-big .dashicons{font-size:1.45em;margin-right:6px;margin-left:-4px}.imagify-button-primary.imagify-button-primary .dashicons,.imagify-button-secondary.imagify-button-secondary .dashicons,.imagify-button.imagify-button .dashicons,.imagify-notice .button .dashicons,.imagify-settings .button .dashicons,.imagify-welcome .button .dashicons{vertical-align:middle}[class*=imagify-] .button-text{display:inline-block;vertical-align:middle}.media-frame-content .imagify-button-primary,.wp_attachment_image .imagify-button-primary{padding:0 10px 1px;margin:0 5px 2px 0;font-size:13px;line-height:26px;-webkit-box-shadow:0 3px 0 rgba(51,142,166,1);box-shadow:0 3px 0 rgba(51,142,166,1)}.imagify-title.imagify-title{position:relative;padding:10px 30px;font-size:23px;background:#1F2332;color:#FFF}.imagify-welcome .imagify-logo{opacity:1}.imagify-welcome .imagify-title{display:-webkit-box;display:-ms-flexbox;display:flex;padding:20px 30px}.imagify-settings .imagify-title{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.imagify-settings .imagify-logo-block{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-negative:0;flex-shrink:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding:0;margin-right:35px;color:inherit}.imagify-logo-block sup{color:#1F2332}.imagify-settings .imagify-title+.imagify-notice{margin:0;border-right:1px solid #D9D9D9;padding-top:15px;padding-bottom:15px}.imagify-title .title-text{font-size:28px;font-weight:700;color:#FFF}.imagify-lb-icon{padding-right:18px}.imagify-lb-text img{margin-bottom:.15em}.imagify-lb-text{font-size:23px;font-weight:700;color:#FFF}.imagify-logo{display:block;opacity:.4}.imagify-section,.imagify-settings div.submit,.imagify-sub-header,.imagify-sub-title.imagify-sub-title{margin:0;padding:20px;background:#F2F5F7}.imagify-section-positive,.imagify-sub-title.imagify-sub-title{padding-left:40px}.imagify-section-positive{background:#8cc152;color:#FFF}.imagify-section-positive p{color:#FFF}.imagify-section-gray{background:#D9E4EB}.imagify-section-gray .imagify-count-title{color:#4a4a4a}.imagify-section p:last-child{margin-bottom:0}.imagify-settings .imagify-documentation-link-box{display:-webkit-box;display:-ms-flexbox;display:flex;padding:12px 13px 14px;border:1px solid #40b1d0;color:#E5EBEF;border-radius:3px}.imagify-documentation-link-icon{width:23px;height:31px;font-size:2.6em;margin-right:15px;line-height:1.3}.imagify-documentation-link-box span{font-size:12px}.imagify-documentation-link-box a{font-weight:700}@media (max-width:1120px){.imagify-settings .imagify-title{-ms-flex-wrap:wrap;flex-wrap:wrap}}.imagify-settings-section{padding:10px 20px}.imagify-account-info-col .imagify-settings-section{padding-right:0}.imagify-settings-main-content,.imagify-welcome .imagify-settings-section{border:1px solid #D9D9D9;border-top-width:0;background:#FFF}.imagify-settings-main-content{padding-bottom:20px}.imagify-settings-main-content .imagify-setting-line,.imagify-settings-main-content p{font-size:14px;line-height:1.5}.imagify-settings-main-content .code{max-height:10em;padding:3px 5px 2px;overflow:auto;background:#EAEAEA;background:rgba(0,0,0,.07)}.imagify-settings-main-content+.imagify-settings-main-content{margin-top:20px;border-top-width:1px}.imagify-br{line-height:2}p.imagify-section-title.imagify-section-title{font-size:20px;margin-top:-.3em;margin-bottom:-.6em}.imagify-rate-us.imagify-rate-us{text-align:right;margin:-1em -2.4em -1em 0;color:#FFF}.imagify-rate-us a{color:#40B1D0}.imagify-rate-us .stars{display:inline-block;margin:2px 0 0 10px;text-decoration:none;letter-spacing:.2em;vertical-align:-1px}.imagify-rate-us .stars .dashicons:before{font-size:18px}.imagify-rate-us a:focus,.imagify-rate-us a:hover{color:#FEE102}@media (max-width:1220px){.imagify-rate-us.imagify-rate-us{position:static;margin-bottom:0;text-align:left}.imagify-rate-us.imagify-rate-us br{display:none}.imagify-rate-us .stars{display:block;margin-left:0}}.imagify-important{color:#F5A623}.imagify-settings .imagify-success,.imagify-success{color:#8BC34A}.imagify-info,.imagify-info a{color:#7A8996;font-size:12px}.imagify-info{position:relative;display:inline-block;padding-left:25px}.imagify-info .dashicons{position:absolute;left:0;top:0;color:#40B1D0}.imagify-checkbox.imagify-checkbox:checked,.imagify-checkbox.imagify-checkbox:not(:checked),.imagify-settings.imagify-settings [type=checkbox]:checked,.imagify-settings.imagify-settings [type=checkbox]:not(:checked){position:absolute;opacity:.01}.imagify-checkbox.imagify-checkbox:checked:focus,.imagify-checkbox.imagify-checkbox:not(:checked):focus,.imagify-settings.imagify-settings [type=checkbox]:checked:focus,.imagify-settings.imagify-settings [type=checkbox]:not(:checked):focus{-webkit-box-shadow:none!important;box-shadow:none!important;outline:0!important;border:0!important}.imagify-checkbox.imagify-checkbox:checked+label,.imagify-checkbox.imagify-checkbox:not(:checked)+label,.imagify-settings [type=checkbox]:checked+label,.imagify-settings [type=checkbox]:not(:checked)+label{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:24px;padding-left:40px;cursor:pointer;font-size:14px;font-weight:700;color:#2E3243}.imagify-checkbox.imagify-checkbox:checked+label:before,.imagify-checkbox.imagify-checkbox:not(:checked)+label:before,.imagify-settings [type=checkbox]:checked+label:before,.imagify-settings [type=checkbox]:not(:checked)+label:before{content:'';position:absolute;left:0;top:0;width:22px;height:22px;border:2px solid #8BA6B4;background:#FFF;border-radius:3px}.imagify-checkbox.imagify-checkbox:checked+label:after,.imagify-checkbox.imagify-checkbox:not(:checked)+label:after,.imagify-settings [type=checkbox]:checked+label:after,.imagify-settings [type=checkbox]:not(:checked)+label:after{content:"â";position:absolute;font-size:1.4em;top:-2px;left:4.5px;color:#8BA6B4;font-weight:400;-webkit-transition:all .2s;-o-transition:all .2s;transition:all .2s}.imagify-checkbox.imagify-checkbox[disabled]:checked+label:before,.imagify-checkbox.imagify-checkbox[disabled]:not(:checked)+label:before,.imagify-settings [type=checkbox][disabled]:checked+label:before,.imagify-settings [type=checkbox][disabled]:not(:checked)+label:before{border-color:#ccc;background:#ddd}.imagify-checkbox.imagify-checkbox:not(:checked)+label:after,.imagify-settings [type=checkbox]:not(:checked)+label:after{opacity:0;-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0)}.imagify-checkbox.imagify-checkbox:checked+label:after,.imagify-settings [type=checkbox]:checked+label:after{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.medium.imagify-checkbox:checked+label:before,.medium.imagify-checkbox:not(:checked)+label:before{width:22px;height:22px;border-width:1.5px;border-radius:2px;margin-top:0}.medium.imagify-checkbox:checked+label:after,.medium.imagify-checkbox:not(:checked)+label:after{font-size:1.1em;left:-17px;top:3px}.imagify-settings .mini[type=checkbox]:checked+label:before,.imagify-settings .mini[type=checkbox]:not(:checked)+label:before,.mini.imagify-checkbox:checked+label:before,.mini.imagify-checkbox:not(:checked)+label:before{width:15px;height:15px;border-width:1px;border-radius:2px;margin-top:0}.imagify-settings .mini[type=checkbox]:checked+label:after,.imagify-settings .mini[type=checkbox]:not(:checked)+label:after,.mini.imagify-checkbox:checked+label:after,.mini.imagify-checkbox:not(:checked)+label:after{font-size:.9em;left:-21px;top:-.5px}.imagify-checkbox.imagify-checkbox:checked:focus+label:before,.imagify-checkbox.imagify-checkbox:not(:checked):focus+label:before,.imagify-settings [type=checkbox]:checked:focus+label:before,.imagify-settings [type=checkbox]:not(:checked):focus+label:before{border-style:dotted;border-color:#40b1d0}.imagify-check-group{padding-left:2px;margin-bottom:0}.imagify-check-group.imagify-is-scrollable{height:15em;overflow-y:auto;padding:8px;margin:1.5em 0 0 -8px;background:#F4F7F9;border:1px solid #D2D3D6;border-radius:3px}.imagify-is-scrollable legend+p{margin-top:0}.imagify-is-scrollable [type=checkbox]:checked+label:before,.imagify-is-scrollable [type=checkbox]:not(:checked)+label:before{background:#F4F7F9}.imagify-settings .imagify-check-group.imagify-check-group label{color:#338EA6;font-weight:500}.imagify-inline-options{position:relative;display:table;width:100%;max-width:600px;border-collapse:collapse}.imagify-inline-options input[type=radio]:checked,.imagify-inline-options input[type=radio]:not(:checked){position:absolute;left:5px;top:5px;display:none}.imagify-inline-options input[type=radio]:checked+label,.imagify-inline-options input[type=radio]:not(:checked)+label{position:relative;display:table-cell;padding:13px 10px;text-align:center;font-weight:600;font-size:16px;text-transform:uppercase;letter-spacing:.1em;color:#FFF;background:#2E3243;-webkit-box-shadow:0 -3px 0 rgba(0,0,0,.1) inset;box-shadow:0 -3px 0 rgba(0,0,0,.1) inset;z-index:2;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-inline-options input[type=radio]:checked+label:first-of-type,.imagify-inline-options input[type=radio]:not(:checked)+label:first-of-type{border-radius:3px 0 0 3px}.imagify-inline-options input[type=radio]:checked+label:last-of-type,.imagify-inline-options input[type=radio]:not(:checked)+label:last-of-type{border-radius:0 3px 3px 0}.imagify-inline-options input[type=radio]:checked+label{background:#8BC34A}.imagify-inline-options .imagify-info{margin-top:15px}.imagify-account-info-col,.imagify-col.imagify-col.imagify-account-info-col{width:380px;max-width:100%;padding:0 20px 0 0}.imagify-col.imagify-col.imagify-shared-with-account-col,.imagify-shared-with-account-col{width:calc(100% - 380px);padding:0}.imagify-account-info-col .imagify-options-title{padding:24px 26px;color:#FFF;background:#1F2332}.imagify-block-secondary{padding:26px 26px 35px;border:1px solid #75A345;background:#8BC34A;border-radius:3px;color:#FFF}.imagify-account-info-col .imagify-block-secondary.imagify-block-secondary h3,.imagify-block-secondary.imagify-block-secondary p{color:inherit}.imagify-account-info-col .imagify-col-content h3:first-child{margin-top:0}.imagify-account-info-col .imagify-col-content h3{font-size:19px}.imagify-account-info-col .imagify-col-content p{margin:1.5em 0}.imagify-account-info-col .imagify-col-content p:first-child{margin-top:0}.imagify-user-plan-label{float:right;margin-top:-4px;padding:2px 10px;border:2px solid #40B1D0;font-size:14px;text-transform:uppercase;letter-spacing:.02em;color:#40B1D0;border-radius:3px}.imagify-modal .h2,.imagify-modal .h3{letter-spacing:.075em;text-align:center;font-weight:400}.imagify-user-plan-label:empty{display:none}.imagify-columns{padding:15px 0;counter-reset:cols}.imagify-columns [class^=col-]{float:left;-webkit-box-sizing:border-box;box-sizing:border-box}.imagify-columns .col-1-3{width:33.333%;padding-left:28px}.imagify-columns .col-2-3{width:66.666%;padding-left:28px}.imagify-columns .col-1-2{width:50%;padding:0 20px}@media (max-width:830px){.imagify-columns [class^=col-]{float:none;margin-bottom:1.5em}.imagify-columns .col-1-2,.imagify-columns .col-1-3{width:auto;padding:0 28px;clear:both;padding-top:1em}}.column-imagify_optimized_file.column-imagify_optimized_file{width:300px;text-align:center;vertical-align:middle}.column-imagify_optimized_file>*{max-width:21em;margin:0 auto}@media (min-width:1151px) and (max-width:1800px){.column-imagify_optimized_file.column-imagify_optimized_file{width:21em}}@media (min-width:783px) and (max-width:1150px){.column-imagify_optimized_file.column-imagify_optimized_file{width:13em}table.media .column-title .has-media-icon~.row-actions.row-actions{margin-left:0}}@media (max-width:782px){table.media .column-imagify_optimized_file.column-imagify_optimized_file{text-align:left}table.media .imagify-datas-actions-links,table.media .imagify-datas-more-action{text-align:center}table.media .column-imagify_optimized_file .imagify-datas-actions-links a,table.media .column-imagify_optimized_file>*{max-width:100%;margin-left:0}}@media (min-width:783px) and (max-width:1150px),(max-width:360px){table.media .imagify-hide-if-small{position:absolute;margin:-1px;padding:0;height:1px;width:1px;overflow:hidden;clip:rect(0 0 0 0);border:0;word-wrap:normal!important}}.compat-field-imagify .label{vertical-align:top}.compat-field-imagify ul.imagify-datas-list{margin-top:7px;font-size:11px}ul.imagify-datas-list.imagify-datas-list{margin:0 auto;color:#555}ul.imagify-datas-list .big{font-size:12px;color:#40B1D0}li.imagify-data-item{clear:both;margin-bottom:2px}ul.imagify-datas-list .imagify-data-item span.data,ul.imagify-datas-list .imagify-data-item strong{float:left;width:38%;-webkit-box-sizing:border-box;box-sizing:border-box}ul.imagify-datas-list .imagify-data-item span.data{width:62%;padding-right:5px;text-align:left}.compat-field-imagify .imagify-datas-list .imagify-data-item .data{width:130px;text-align:left;font-weight:700}ul.imagify-datas-list .imagify-data-item strong{text-align:left;padding-left:5px}.media-sidebar .imagify-datas-list .imagify-data-item .data{width:auto;float:none}.media-sidebar .imagify-datas-list .imagify-data-item strong{display:inline-block;width:auto;float:none}.media-sidebar .imagify-datas-list .imagify-data-item .imagify-chart{float:left}.imagify-datas-more-action.imagify-datas-more-action{margin:.4em auto;background:-webkit-gradient(linear,left top,left bottom,from(transparent),color-stop(49%,transparent),color-stop(50%,rgba(0,0,0,.075)),color-stop(58%,rgba(0,0,0,.075)),color-stop(58%,transparent),to(transparent));background:-o-linear-gradient(top,transparent,transparent 49%,rgba(0,0,0,.075) 50%,rgba(0,0,0,.075) 58%,transparent 58%,transparent);background:linear-gradient(to bottom,transparent,transparent 49%,rgba(0,0,0,.075) 50%,rgba(0,0,0,.075) 58%,transparent 58%,transparent)}.imagify-datas-more-action a{display:inline-block;padding:0 5px;background:#40B1D0;color:#FFF;text-transform:uppercase;font-size:9px;font-weight:700;line-height:1.9;text-decoration:none}.imagify-datas-more-action a.is-open{background:#555}.imagify-datas-more-action a.is-open .dashicons{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.imagify-datas-more-action a .dashicons{font-size:14px;vertical-align:middle;line-height:.8}.imagify-datas-more-action a .dashicons:before{vertical-align:middle;line-height:20px}.imagify-datas-more-action .the-text{display:inline-block;vertical-align:middle;height:auto;line-height:inherit}ul.imagify-datas-details.imagify-datas-details{margin:.7em auto}.imagify-datas-details strong{color:#40B1D0}.imagify-datas-details .original{color:#555}.imagify-datas-actions-links{border-top:2px solid transparent;padding-top:5px;font-size:11px}.nggform .imagify-datas-actions-links{position:relative;z-index:2}.nggform .row-actions{z-index:1}.imagify-datas-actions-links a{position:relative;display:inline-block;padding-left:17px;text-decoration:none;font-weight:600}.compat-field-imagify .imagify-datas-actions-links{max-width:300px}.misc-pub-imagify .imagify-datas-actions-links{border-top:2px solid #f2f2f2;padding-bottom:5px}.column-imagify_optimized_file .imagify-datas-actions-links a{margin:0 .7em;padding-left:15px}.compat-field-imagify .imagify-datas-actions-links a,.misc-pub-imagify .imagify-datas-actions-links a{float:left;width:50%}.media-sidebar .compat-field-imagify .imagify-datas-actions-links a,.submitbox .misc-pub-imagify .imagify-datas-actions-links a{display:block;width:auto;float:none}.media-sidebar .compat-field-imagify .imagify-datas-actions-links br,.submitbox .misc-pub-imagify .imagify-datas-actions-links br{display:none}.imagify-datas-actions-links a:only-child{float:none;width:auto}.imagify-datas-details.is-open+.imagify-datas-actions-links{border-top-color:rgba(0,0,0,.075)}.imagify-datas-actions-links .dashicons{position:absolute;left:0;top:4px;width:12px;margin-right:2px;font-size:11px}.imagify-account,.imagify-account-link{padding-right:15px}.imagify-meteo-icon{display:inline-block;height:38px;vertical-align:middle;margin-right:10px}.imagify-user-plan{color:#40b1d0}.imagify-meteo-title.imagify-meteo-title{color:#FFF;font-size:17px}.imagify-space-left>p{color:#FFF}[class^=imagify-bar-]{position:relative;height:8px;width:100%;background:#60758D;color:#FFF;font-size:10px}.imagify-progress{height:8px;-webkit-transition:width .3s;-o-transition:width .3s;transition:width .3s}.imagify-bar-positive .imagify-progress{background:#8CC152}.imagify-bar-positive .imagify-barnb{color:#8CC152}.imagify-bar-primary .imagify-progress{background:#40B1D0}.imagify-bar-primary .imagify-barnb{color:#40B1D0}.imagify-bar-negative .imagify-progress{background:#D2D3D6}.imagify-bar-negative .imagify-barnb{color:#7A8996}.imagify-bar-neutral .imagify-progress{background:#F5A623}.imagify-space-left .imagify-bar-negative .imagify-progress{background:#C51162}.imagify-btn-ghost{display:inline-block;height:auto;padding:7px 10px;border:1px solid #FFF;text-align:center;background:0 0;color:#FFF;border-radius:3px;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-btn-ghost:focus,.imagify-btn-ghost:hover{background:#FFF;color:#888}.imagify-error{background:#D0021B;color:#FFF}.imagify-settings-section .imagify-error{display:inline-block;padding:7px 10px;margin:10px 0 0 45px;border-radius:3px}.imagify-settings-section .imagify-error code{font-weight:400}.imagify-settings-section .imagify-error.hidden{display:none}.imagify-warning{background:#f5a623;color:#FFF;text-shadow:0 0 2px rgba(0,0,0,.2)}.imagify-modal{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.js .imagify-modal{display:none;position:fixed;top:0;right:0;bottom:0;left:0;background-color:#1F2332;background-color:rgba(31,35,50,.95);z-index:99999}.imagify-modal-content{-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;width:800px;max-width:95%;max-height:90vw;overflow:auto;padding:20px 25px;margin:1em auto;background:#FFF;-webkit-box-shadow:1px 1px 4px rgba(0,0,0,.7);box-shadow:1px 1px 4px rgba(0,0,0,.7);border-radius:3px}#imagify-visual-comparison .imagify-modal-content,.imagify-visual-comparison .imagify-modal-content{max-width:1400px;background:0 0;padding:5px;-webkit-box-shadow:none;box-shadow:none;border-radius:0}.imagify-modal .h2{margin:.5em 0;color:#8ba6b4;font-size:24px}.imagify-modal .h3{color:#40b1d0;font-size:18px}.imagify-modal .close-btn{display:none;visibility:hidden;position:absolute;right:20px;top:20px;font-size:1.2em;border:0;background:0 0;border-radius:0;cursor:pointer}.imagify-modal .close-btn i{margin-left:-2px}.imagify-modal .close-btn:focus,.imagify-modal .close-btn:hover{color:#40b1d0}.js .imagify-modal .close-btn{display:block;visibility:visible}.imagify-visual-comparison .close-btn,.wp_attachment_image #imagify-visual-comparison .close-btn{top:0}.imagify-visual-comparison .imagify-modal-content,.wp_attachment_image #imagify-visual-comparison .imagify-modal-content{padding-top:40px}.imagify-col{float:left;width:50%;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-preferred-size:50%;flex-basis:50%;padding-right:20px}.imagify-col+.imagify-col{padding-right:0;padding-left:20px}.imagify-col:target{-webkit-animation:hello 1s 3 linear backwards;animation:hello 1s 3 linear backwards}@-webkit-keyframes hello{0%,100%{background:#FFF}50%{background:#F4F7F9}}@keyframes hello{0%,100%{background:#FFF}50%{background:#F4F7F9}}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/bulk.css b/wp-content/plugins/imagify/assets/css/bulk.css
new file mode 100644
index 00000000..673258cf
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/bulk.css
@@ -0,0 +1,1107 @@
+/* Doughnut legend */
+#imagify-overview-chart-legend {
+ overflow: hidden;
+}
+
+.imagify-doughnut-legend {
+ margin-top: 38px;
+ list-style: none;
+}
+
+.imagify-doughnut-legend li {
+ display: block;
+ padding-left: 30px;
+ position: relative;
+ margin-bottom: 15px;
+ border-radius: 5px;
+ padding: 3px 8px 2px 31px;
+ font-size: 13px;
+ cursor: default;
+ -webkit-transition: background-color 200ms ease-in-out;
+ -moz-transition: background-color 200ms ease-in-out;
+ -o-transition: background-color 200ms ease-in-out;
+ transition: background-color 200ms ease-in-out;
+}
+
+.imagify-doughnut-legend li span {
+ display: block;
+ position: absolute;
+ left: 0; top: 0;
+ width: 25px;
+ height: 25px;
+ border-radius: 50%;
+}
+
+.imagify-global-optim-phrase {
+ width: 180px;
+ padding-top: 20px;
+ font-size: 14px;
+ text-align: center;
+}
+
+.imagify-total-percent {
+ color: #46b1ce;
+}
+
+.imagify-overview-chart-container {
+ float: left;
+ margin-right: 20px;
+}
+.imagify-chart-percent {
+ position: absolute;
+ left: 0; right: 0;
+ top: 50%;
+ margin-top: -.5em;
+ line-height: 1;
+ text-align: center;
+ font-size: 55px;
+ font-weight: bold;
+ color: #46B1CE;
+}
+.imagify-chart-percent span {
+ font-size: 20px;
+ vertical-align: super;
+}
+
+.media_page_imagify-bulk-optimization .media-item,
+body[class*="_imagify-ngg-bulk-optimization"] .media-item {
+ margin: 0;
+}
+
+.media_page_imagify-bulk-optimization .media-item .progress,
+body[class*="_imagify-ngg-bulk-optimization"] .media-item .progress {
+ float: none;
+ width: 100%;
+ height: 8px;
+ margin: 0;
+ overflow: visible;
+ background: #1F2331;
+ box-shadow: 0;
+ border-radius: 0;
+}
+
+.media_page_imagify-bulk-optimization .media-item .percent,
+body[class*="_imagify-ngg-bulk-optimization"] .media-item .percent {
+ position: absolute;
+ top: 6px;
+ right: 0;
+ text-shadow: none;
+ width: auto;
+ padding: 0 5px;
+ line-height: 1.85;
+ font-size: 14px;
+ font-weight: bold;
+ color: #40B1D0;
+}
+
+.media_page_imagify-bulk-optimization .media-item .progress,
+.media_page_imagify-bulk-optimization .media-item .percent,
+body[class*="_imagify-ngg-bulk-optimization"] .media-item .progress,
+body[class*="_imagify-ngg-bulk-optimization"] .media-item .percent {
+ text-align: right;
+}
+.media_page_imagify-bulk-optimization .media-item .progress .bar,
+body[class*="_imagify-ngg-bulk-optimization"] .media-item .progress .bar {
+ position: relative;
+ width: 1px;
+ height: 8px;
+ margin-top: 0;
+ background: #46B1CE;
+ border-radius: 0;
+ -webkit-transition: width .5s;
+ transition: width .5s;
+}
+
+#imagify-bulk-action {
+ padding: 11px 20px;
+}
+
+/* Bulk overview columns */
+.imagify-columns .col-overview.col-overview {
+ width: calc(100% - 465px);
+ padding-left: 20px;
+}
+.imagify-columns .col-statistics.col-statistics {
+ width: 60%;
+}
+.imagify-columns .col-chart.col-chart {
+ width: 40%;
+}
+
+@media (max-width: 1520px) and (min-width: 1381px), (max-width: 1086px) {
+ .imagify-columns .col-statistics.col-statistics,
+ .imagify-columns .col-chart.col-chart {
+ width: 50%;
+ }
+}
+
+@media (max-width: 1380px) and (min-width: 1246px), (max-width: 380px) {
+ .imagify-overview-chart-container {
+ float: none;
+ margin-right: 0;
+ }
+ .imagify-doughnut-legend {
+ margin-top: 18px;
+ }
+ .imagify-global-optim-phrase {
+ padding-top: 0;
+ width: auto;
+ }
+}
+
+@media (max-width: 808px) {
+ .imagify-columns .col-statistics.col-statistics,
+ .imagify-columns .col-chart.col-chart {
+ width: auto;
+ float: none;
+ padding: 0;
+ }
+ .imagify-columns .col-chart.col-chart {
+ margin-top: 3em;
+ }
+}
+
+/* Header */
+.imagify-sep-v {
+ width: 1px;
+ background: rgba(255, 255, 255, .2);
+}
+.base-transparent {
+ background: transparent;
+}
+
+[class^="imagify-bar-"].right-outside-number {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ padding-right: 4.5em;
+}
+.right-outside-number .imagify-barnb {
+ display: block;
+ margin-right: -5.25em;
+ text-align: right;
+ font-weight: bold;
+ line-height: .8;
+}
+
+.imagify-h2-like {
+ margin: 0 0 .5em 0;
+ padding-bottom: .5em;
+ border-bottom: 1px solid #E9EFF2;
+ font-size: 24px;
+ color: #000;
+ font-weight: bold;
+}
+.imagify-h2-like .dashicons,
+.imagify-h2-like .dashicons:before {
+ font-size: 38px;
+ height: 38px;
+ width: 38px;
+ margin-right: 12px;
+ vertical-align: -5px;
+ color: #40B1D0;
+}
+.imagify-info-block {
+ position: relative;
+ padding: 10px;
+ padding-left: 42px;
+ background: #D9E4EB;
+ border-radius: 4px;
+ line-height: 1.6;
+}
+.imagify-list-infos {
+ margin: 0;
+ padding: 0;
+}
+.imagify-list-infos li {
+ display: flex;
+ align-items: center;
+ padding: 15px 5px;
+ text-align: left;
+ font-size: 14px;
+ line-height: 1.5;
+ color: #626E7B;
+}
+.imagify-list-infos li:first-child {
+ padding-top: 5px;
+}
+.imagify-list-infos li:last-child {
+ padding-bottom: 5px;
+}
+.imagify-list-infos li + li {
+ border-top: 1px solid #E9EFF2;
+}
+
+.imagify-info-icon {
+ flex-grow: 0;
+ flex-basis: 50px;
+}
+.imagify-info-icon + span {
+ padding-left: 20px;
+}
+
+.imagify-list-infos a:before {
+ content: '';
+ display: block;
+}
+
+/* Some main sections/content */
+.imagify-bulk .imagify-settings-section {
+ border: 1px solid #D9D9D9;
+ border-top: 0;
+ background: #FFF;
+ color: #4A4A4A;
+}
+.imagify-bulk p,
+.imagify-bulk li,
+.imagify-bulk h3 {
+ color: #4A4A4A;
+}
+.imagify-bulk .imagify-settings-section h3 {
+ margin-bottom: 2em;
+}
+
+/* Account information col */
+.imagify-account-info-col .imagify-options-title {
+ display: flex;
+ align-items: center;
+}
+.imagify-account-info-col p.imagify-meteo-title {
+ margin: 0;
+ font-size: 24px;
+ font-weight: bold;
+ color: #FFF;
+}
+.imagify-account-info-col .imagify-options-title > a {
+ flex-basis: 100px;
+ margin-left: auto;
+ margin-right: 10px;
+ text-decoration: underline;
+ font-size: 12px;
+}
+.imagify-account-info-col .imagify-meteo-title .dashicons,
+.imagify-account-info-col .imagify-meteo-title .dashicons:before {
+ font-size: 38px;
+ width: 38px;
+ height: 38px;
+ margin-right: 4px;
+ color: #40B1D0;
+}
+.imagify-col-content {
+ background: #F4F7F9;
+ border-left: 1px solid #ddd;
+ border-right: 1px solid #ddd;
+}
+.imagify-col-content .imagify-block-secondary {
+ margin-left: -1px;
+ margin-right: -1px;
+}
+.imagify-col-content .imagify-space-left {
+ margin: 15px 30px 15px 0;
+}
+.imagify-col-content .imagify-space-left p {
+ margin: 0 0 10px 0;
+ font-size: 19px;
+ font-weight: 500;
+ color: #343A49;
+}
+.imagify-col-content .imagify-meteo-icon {
+ height: 64px;
+ margin: 15px 15px 15px 20px;
+}
+.imagify-col-content .imagify-section-title + p {
+ margin-top: 10px;
+}
+
+.imagify-account-info-col .imagify-h3-like.imagify-h3-like {
+ color: inherit;
+}
+
+/* Tooltips */
+.imagify-title .imagify-tooltips {
+ position: absolute;
+ top: 100%;
+ left: 0;
+}
+.imagify-tooltips .icon-round {
+ float: left;
+ display: inline-block;
+ width: 28px;
+ height: 28px;
+ border: 1px solid #FFF;
+ margin-right: 8px;
+ margin-bottom: 8px;
+ font-size: 17px;
+ font-style: italic;
+ line-height: 29px;
+ font-weight: bold;
+ text-align: center;
+ border-radius: 50%;
+}
+.imagify-tooltips .tooltip-content {
+ display: block;
+ position: relative;
+ max-width: 250px;
+ padding: 7px 15px 8px;
+ background: #2e3242;
+ color: #FFF;
+ font-size: 10px;
+ border-radius: 3px;
+}
+.imagify-tooltips.right .tooltip-content {
+ margin-left: 12px;
+}
+.imagify-tooltips.bottom .tooltip-content {
+ margin-top: 4px;
+}
+.imagify-inline-options label .tooltip-content {
+ position: absolute;
+ left: 0; right: 0;
+ top: 100%;
+ text-transform: none;
+ font-size: 10px;
+ letter-spacing: 0;
+ text-align: center;
+}
+.imagify-tooltips .tooltip-content:after {
+ content: "";
+ position: absolute;
+}
+.imagify-tooltips.right .tooltip-content:after {
+ top: 16px; left: -6px;
+ border-right: 8px solid #2e3242;
+ border-top: 6px solid transparent;
+ border-bottom: 6px solid transparent;
+}
+.imagify-tooltips.bottom .tooltip-content:after {
+ top: -5px; left: 50%;
+ margin-left: -3px;
+ border-bottom: 6px solid #2e3242;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+}
+.imagify-space-tooltips .tooltip-content {
+ max-width: 280px;
+ margin-top: 20px;
+ margin-left: 0;
+ padding: 5px 15px 5px;
+ font-size: 13px;
+ background: #40B1D0;
+ box-shadow: 0 3px 0 #338EA6;
+}
+.imagify-space-tooltips .tooltip-content:after {
+ top: -14px;
+ left: 50%;
+ margin-left: -7px;
+ border: 0 none;
+ border-bottom: 15px solid #40B1D0;
+ border-left: 15px solid transparent;
+ border-right: 15px solid transparent;
+}
+.tooltip-content.tooltip-table {
+ display: table;
+ width: 100%;
+}
+.tooltip-content.tooltip-table > * {
+ display: table-cell;
+ vertical-align: middle;
+}
+.tooltip-content .cell-icon {
+ width: 28px;
+}
+.tooltip-content .cell-icon .icon {
+ margin-bottom: 0;
+}
+.tooltip-content .cell-text {
+ padding: 5px 10px 5px 0;
+ line-height: 1.3;
+}
+.tooltip-content .cell-sep {
+ width: 1px;
+ background: rgba(255, 255, 255, .4);
+}
+.tooltip-content .cell-cta {
+ padding-left: 10px;
+}
+.tooltip-content .cell-cta a {
+ display: block;
+ color: #FFF;
+ width: 100%;
+ height: 100%;
+ white-space: nowrap;
+}
+
+/* Number display */
+.imagify-number-you-optimized {
+ margin-bottom: 1.35em;
+ overflow: hidden;
+}
+.imagify-number-you-optimized .number {
+ display: table-cell;
+ padding-right: 15px;
+ font-size: 48px;
+ font-weight: bold;
+ line-height: 1;
+ vertical-align: middle;
+ white-space: nowrap;
+ color: #000;
+}
+.imagify-number-you-optimized [id="imagify-total-optimized-attachments-pct"] {
+ color: #40B1D0;
+}
+.imagify-number-you-optimized .text {
+ display: table-cell;
+ vertical-align: middle;
+ overflow: hidden;
+ font-size: 12px;
+ color: #626E7B;
+}
+.imagify-number-you-optimized > p {
+ display: table;
+}
+
+/* Number and bars */
+.imagify-bars {
+ padding-right: 15px;
+}
+.imagify-bars p {
+ font-size: 12px;
+ margin-bottom: 5px;
+}
+.imagify-bars + .imagify-number-you-optimized {
+ border-bottom: 0;
+ padding-top: 0.85em;
+}
+.imagify-bars + .imagify-number-you-optimized p {
+ color: #46b1ce;
+}
+
+/* Table */
+.imagify-bulk-table {
+ margin-top: 2em;
+}
+
+.imagify-table-header {
+ justify-content: space-between;
+ padding: 15px 25px;
+ background: #343A49;
+ color: #FFF;
+}
+.imagify-newbie {
+ margin-top: 4em;
+ position: relative;
+ overflow: visible;
+}
+.imagify-newbie .imagify-new-feature.imagify-new-feature {
+ position: absolute;
+ top: 0;
+ left: 25px;
+ transform: translateY(-50%);
+ margin: 0;
+ padding: 8px 20px;
+ font-size: 14px;
+ letter-spacing: .02em;
+ text-transform: uppercase;
+ font-weight: bold;
+ color: #FFF;
+ background: #8BC34A;
+}
+.imagify-newbie .imagify-table-header {
+ padding: 30px 25px;
+ border: 2px solid #8BC34A;
+ background: #F3F9EC;
+}
+.imagify-th-titles .dashicons,
+.imagify-th-titles .dashicons:before {
+ width: 38px;
+ height: 38px;
+ margin-right: 20px;
+ font-size: 38px;
+ color: #40B1D0;
+}
+.imagify-newbie .imagify-th-titles .dashicons:before {
+ color: #8BC34A;
+}
+.imagify-th-title.imagify-th-title.imagify-th-title {
+ margin: 0;
+ font-size: 24px;
+ font-weight: 500;
+ color: #FFF;
+}
+.imagify-newbie .imagify-th-title.imagify-th-title {
+ color: #343A49;
+}
+.imagify-th-subtitle.imagify-th-subtitle.imagify-th-subtitle {
+ margin: 0 0 5px;
+ font-size: 14px;
+ color: #7A8996;
+ font-weight: 500;
+}
+.imagify-th-action .imagify-button-clean {
+ font-size: 12px;
+ color: #7A8996;
+}
+.imagify-th-action .imagify-is-active {
+ color: #FFF;
+}
+.imagify-th-action .button:hover,
+.imagify-th-action .button:focus {
+ color: #FFF;
+}
+
+.imagify-bulk-table table {
+ width: 100%;
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+.imagify-bulk-table td {
+ padding: 20px;
+}
+.imagify-bulk-table-details {
+ border-bottom: 2px solid #E5EBEF;
+}
+.imagify-bulk-table-details thead tr,
+.imagify-bulk-table-details thead th {
+ background: #4A5362;
+}
+.imagify-bulk-table-details thead th {
+ padding: 12px 20px;
+ text-align: left;
+ font-weight: bold;
+ color: #E5EBEF;
+ font-size: 12px;
+}
+.imagify-bulk-table-details tbody tr:nth-child(odd) td {
+ background: #F2F5F7;
+}
+.imagify-bulk-table-content {
+ border: 1px solid #D3D3D3;
+ border-top: 0;
+}
+.imagify-bulk-table-footer {
+ padding: 20px;
+ color: #626E7B;
+ background: #F2F5F7;
+}
+
+.imagify-bulk-table tbody tr + tr {
+ border-top: 3px solid #F2F5F7;
+}
+.imagify-bulk-table tbody tr,
+.imagify-bulk-table tbody td {
+ background: #FFF;
+}
+
+.imagify-bulk-table .imagify-row-progress {
+ display: none;
+}
+
+.imagify-bulk-table .imagify-row-progress {
+ height: 8px;
+ padding: 0;
+}
+.imagify-bulk-table .imagify-no-uploaded-yet td {
+ height: 200px;
+ font-size: 17px;
+ letter-spacing: .1em;
+ word-spacing: .12em;
+ vertical-align: middle;
+ text-transform: uppercase;
+ font-weight: bold;
+ text-align: center;
+ color: #999;
+ background-color: #FFF;
+}
+
+/* Custom Level Optimization Select */
+.imagify-selector {
+ position: relative;
+}
+.imagify-selector-list {
+ background: #FFF;
+ border: 1px solid #F4F7F9;
+ box-shadow: 0 6px 12px rgba(0, 0, 0, .1);
+ border-radius: 3px;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: .02em;
+}
+.imagify-selector-list li:first-child label {
+ border-radius: 3px 3px 0 0;
+}
+.imagify-selector-list li:last-child label {
+ border-radius: 0 0 3px 3px;
+}
+.imagify-selector-list li {
+ margin: 0;
+}
+.imagify-selector-list li + li {
+ border-top: 1px solid #F4F7F9;
+}
+.imagify-selector-list svg {
+ margin-right: 5px;
+}
+.imagify-selector-list input:checked + label,
+.imagify-selector-list .imagify-selector-current-value label {
+ background: #343A49;
+ color: #FFF;
+}
+.imagify-selector-list input:checked + label:hover,
+.imagify-selector-list .imagify-selector-current-value label:hover,
+.imagify-selector-list label:hover,
+.imagify-selector-list input:focus + label,
+.imagify-selector-list .imagify-selector-current-value input:focus + label {
+ background: #40B1D0;
+ color: #F4F7F9;
+}
+.imagify-selector-list input:checked + label:hover polygon,
+.imagify-selector-list .imagify-selector-current-value label:hover polygon,
+.imagify-selector-list label:hover polygon,
+.imagify-selector-list input:focus + label polygon,
+.imagify-selector-list .imagify-selector-current-value input:focus + label polygon {
+ fill: #FFF;
+}
+.imagify-selector-list input:checked + label:hover polygon[fill="#CCD1D6"],
+.imagify-selector-list .imagify-selector-current-value label:hover polygon[fill="#CCD1D6"],
+.imagify-selector-list label:hover polygon[fill="#CCD1D6"],
+.imagify-selector-list input:focus + label polygon[fill="#CCD1D6"],
+.imagify-selector-list .imagify-selector-current-value input:focus + label polygon[fill="#CCD1D6"] {
+ fill: #3694AE;
+}
+.imagify-selector-list li label {
+ display: block;
+ padding: 10px;
+ transition: all .275s;
+}
+.imagify-selector-list polygon {
+ transition: all .275s;
+}
+.imagify-selector-list {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ transition: all .275s;
+ transform: translateY(-50%);
+}
+.imagify-selector-list[aria-hidden="true"] {
+ opacity: 0;
+ visibility: hidden;
+ transform: translateY(-50%) scale(0);
+}
+.imagify-selector-list[aria-hidden="false"] {
+ opacity: 1;
+ visibility: visible;
+ transform: translateY(-50%) scale(1);
+}
+.button .imagify-selector-current-value-info {
+ position: relative;
+ padding-right: 20px;
+}
+.button .imagify-selector-current-value-info:after {
+ content: '';
+ position: absolute;
+ right: 0;
+ top: 50%;
+ margin-top: -3px;
+ border-top: 6px solid #7A8996;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+}
+
+
+/* Complete row / success */
+.imagify-row-complete {
+ padding: 35px 20px;
+ margin-top: 2em;
+ background: #8BC34A;
+ color: #FFF;
+ text-shadow: 0 0 2px rgba(0,0,0,.1);
+}
+.imagify-row-complete .imagify-ac-chart {
+ margin-top: 3px;
+}
+.imagify-row-complete.imagify-row-complete p {
+ color: #FFF;
+ margin: 0;
+}
+
+@-webkit-keyframes congrate {
+ 0% {
+ opacity: 0;
+ -webkit-transform: scale(1);
+ }
+ 50% {
+ -webkit-transform: scale(1.05);
+ opacity: 1;
+ }
+ 100% {
+ -webkit-transform: scale(1);
+ opacity: 1;
+ }
+}
+
+@keyframes congrate {
+ 0% {
+ opacity: 0;
+ transform: scale(1);
+ }
+ 50% {
+ transform: scale(1.05);
+ opacity: 1;
+ }
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+
+.imagify-row-complete.done {
+ -webkit-animation: congrate 500ms ease-in-out;
+ animation: congrate 500ms ease-in-out;
+}
+
+.imagify-all-complete {
+ margin: 1.5em 0;
+}
+.imagify-all-complete > div {
+ display: inline-block;
+ vertical-align: middle;
+}
+.imagify-ac-report {
+ min-width: 310px;
+ margin-right: 20px;
+}
+.imagify-ac-chart {
+ width: 46px;
+ height: 46px;
+ float: left;
+ margin: 0 20px 0 10px;
+}
+.imagify-ac-report-text {
+ overflow: hidden;
+}
+.imagify-ac-report-text p {
+ line-height: 1.3;
+}
+.imagify-ac-rt-big {
+ font-weight: bold;
+ font-size: 24px;
+ letter-spacing: 0.15em;
+ word-spacing: 0.15em;
+ text-transform: uppercase;
+}
+.imagify-ac-share {
+ text-align: right;
+}
+.imagify-ac-share-content {
+ display: inline-block;
+ padding: 10px 15px;
+ background: rgba(255,255,255,.2);
+}
+.imagify-ac-share-content > * {
+ display: inline-block;
+ vertical-align: middle;
+}
+.imagify-bulk-table .imagify-ac-share-content p {
+ margin-right: 5px;
+}
+.imagify-share-networks,
+.imagify-share-networks li {
+ margin: 0;
+}
+.imagify-share-networks li {
+ display: inline-block;
+}
+.imagify-share-networks a {
+ display: inline-block;
+ vertical-align: -7px;
+ margin: 0 5px;
+ text-decoration: none;
+ color: #FFF;
+}
+
+/* TD's width */
+.imagify-cell-checkbox {
+ width: 35px;
+}
+.imagify-cell-checkbox p {
+ margin: 0;
+}
+.imagify-cell-checkbox-loader {
+ display: block;
+ width: 27px;
+ height: 28px;
+ line-height: 0;
+ animation: loading 4s infinite linear;
+}
+@keyframes loading {
+ 0% {
+ transform: rotate(0);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+.imagify-cell-checkbox-loader.hidden {
+ display: none;
+ animation: none;
+}
+.imagify-cell-title label,
+.imagify-cell-label {
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: .02em;
+ font-weight: bold;
+}
+.imagify-cell-label {
+ margin-right: 10px;
+}
+.imagify-cell-value {
+ font-size: 12px;
+ font-weight: 500;
+ color: #7A8996;
+}
+td.imagify-cell-title {
+ padding-left: 0;
+}
+.imagify-cell-title label,
+.imagify-cell-original-size .imagify-cell-label {
+ color: #1F2332;
+}
+.imagify-cell-optimized-size,
+.imagify-cell-original-size {
+ font-weight: 500;
+ color: #7A8996;
+ font-size: 12px;
+}
+.imagify-cell-optimized-size .imagify-cell-label {
+ color: #338EA6;
+}
+.imagify-cell-count-optimized {
+ font-size: 14px;
+ font-weight: bold;
+ color: #338EA6;
+}
+.imagify-cell-count-errors {
+ color: #C51162;
+ font-weight: bold;
+ font-size: 14px;
+}
+.imagify-cell-count-errors a {
+ margin-left: 5px;
+ color: #7A8996;
+ font-weight: normal;
+ font-size: 12px;
+}
+.imagify-cell-filename {
+ max-width: 200px;
+}
+.imagify-cell-status {
+ max-width: 145px;
+}
+.imagify-cell-status .dashicons-warning {
+ margin-right: 2px;
+}
+.imagify-cell-thumbnails {
+ max-width: 120px;
+}
+
+td.imagify-cell-filename {
+ text-overflow: clip; /* ellipsis replace all the text by ... :`/ */
+ white-space: nowrap;
+ overflow: hidden;
+}
+.imagify-bulk-table .imagify-cell-thumbnails {
+ text-align: center;
+}
+.imagify-cell-percentage,
+.imagify-cell-savings {
+ color: #46B1CE;
+ font-weight: bold;
+}
+.imagify-bulk-table td.imagify-cell-totaloriginal {
+ padding-right: 78px;
+}
+.imagify-cell-totaloriginal {
+ text-align: right;
+}
+.imagify-cell-level {
+ width: 145px;
+}
+.imagify-selector-button.imagify-selector-button {
+ border: 1px solid #FFF;
+ padding: 2px 10px;
+}
+.imagify-selector-button.imagify-selector-button:hover,
+.imagify-selector-button.imagify-selector-button:focus {
+ border-color: #EEE;
+ box-shadow: 0 4px 8px rgba(0,0,0,.1);
+}
+
+.imagiuploaded,
+.imagifilename {
+ display: inline-block;
+ vertical-align: middle;
+ margin-left: 5px;
+ color: #626E7B;
+ font-weight: 500;
+}
+.imagifilename {
+ font-size: 12px;
+}
+.imagiuploaded {
+ width: 33px;
+ height: 33px;
+ margin-right: 5px;
+ margin-left: -8px;
+ overflow: hidden;
+ background: url(../images/upload-image.png) 0 0 no-repeat;
+ background-size: cover;
+}
+.imagiuploaded img {
+ max-width: 100%;
+ height: auto;
+}
+
+.imagistatus {
+ color: #8CA6B3;
+ text-transform: uppercase;
+ font-weight: bold;
+}
+.imagistatus .dashicons {
+ margin-right: 5px;
+}
+.status-compressing {
+ color: #46B1CE;
+}
+.status-error {
+ color: #CE0B24;
+}
+.status-warning {
+ color: #f5a623;
+}
+.status-complete {
+ color: #8CC152;
+}
+
+/* Submit Bulk */
+.imagify-bulk-submit {
+ padding: 15px 0 8px 0;
+}
+
+.imagify-settings .button-primary.button-primary[disabled] {
+ color: #4A4A4A!important;
+ background: #D9E4EB!important;
+ text-shadow: none!important;
+ cursor: not-allowed;
+}
+
+/* Icon rotation */
+.dashicons.rotate {
+ -webkit-animation: icon-rotate 2.6s infinite linear;
+ animation: icon-rotate 2.6s infinite linear;
+}
+.imagify-cell-status .dashicons-admin-generic {
+ transform-origin: 48.75% 51.75%;
+}
+
+@-webkit-keyframes icon-rotate {
+ from {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ to {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@keyframes icon-rotate {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.imagify-col.imagify-col.imagify-account-info-col {
+ width: 465px;
+}
+
+@media (max-width: 1245px) {
+ .imagify-col.imagify-col.imagify-account-info-col {
+ width: auto;
+ max-width: none;
+ float: none;
+ }
+ .imagify-columns .col-overview.col-overview {
+ float: none;
+ width: auto;
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
+
+@media (max-width: 1200px) {
+ .imagify-settings .imagify-title .imagify-logo {
+ display: none;
+ }
+}
+
+@media (max-width: 940px) {
+ .imagify-bulk-table-container tbody,
+ .imagify-bulk-table-container tr {
+ text-align: left;
+ }
+ .imagify-bulk-table-container tbody,
+ .imagify-bulk-table-container tbody tr,
+ .imagify-bulk-table-container tbody td {
+ display: block;
+ }
+ .imagify-bulk-table-container tbody td {
+ padding: 20px;
+ }
+ .imagify-cell-checkbox,
+ .imagify-cell-title {
+ float: left;
+ }
+ .imagify-cell-checkbox {
+ width: 26px;
+ }
+ .imagify-bulk-table-container .imagify-cell-title {
+ padding-left: 10px;
+ width: calc(100% - 96px);
+ }
+ .imagify-cell-title:after,
+ .imagify-cell-count-optimized:before {
+ content: '';
+ display: table;
+ clear: both;
+ width: 100%;
+ }
+ .imagify-cell-count-optimized {
+ clear: both;
+ }
+
+ .imagify-bulk-table-container .imagify-cell-title ~ td {
+ display: inline-block;
+ }
+
+ .imagify-bulk-table-container td.imagify-cell-level {
+ display: block
+ }
+}
+
+@media (max-width: 918px) {
+ .imagify-settings .imagify-title {
+ display: block;
+ }
+ .imagify-settings .imagify-documentation-link-box {
+ display: inline-flex;
+ }
+}
+
+.hidden {
+ display: none;
+}
diff --git a/wp-content/plugins/imagify/assets/css/bulk.min.css b/wp-content/plugins/imagify/assets/css/bulk.min.css
new file mode 100644
index 00000000..f2adb897
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/bulk.min.css
@@ -0,0 +1 @@
+#imagify-overview-chart-legend{overflow:hidden}.imagify-doughnut-legend{margin-top:38px;list-style:none}.imagify-doughnut-legend li{display:block;padding-left:30px;position:relative;margin-bottom:15px;border-radius:5px;padding:3px 8px 2px 31px;font-size:13px;cursor:default;-webkit-transition:background-color .2s ease-in-out;-o-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out}.imagify-doughnut-legend li span{display:block;position:absolute;left:0;top:0;width:25px;height:25px;border-radius:50%}.imagify-global-optim-phrase{width:180px;padding-top:20px;font-size:14px;text-align:center}.imagify-total-percent{color:#46b1ce}.imagify-overview-chart-container{float:left;margin-right:20px}.imagify-chart-percent{position:absolute;left:0;right:0;top:50%;margin-top:-.5em;line-height:1;text-align:center;font-size:55px;font-weight:700;color:#46B1CE}.imagify-chart-percent span{font-size:20px;vertical-align:super}.media_page_imagify-bulk-optimization .media-item,body[class*="_imagify-ngg-bulk-optimization"] .media-item{margin:0}.media_page_imagify-bulk-optimization .media-item .progress,body[class*="_imagify-ngg-bulk-optimization"] .media-item .progress{float:none;width:100%;height:8px;margin:0;overflow:visible;background:#1F2331;-webkit-box-shadow:0;box-shadow:0;border-radius:0}.media_page_imagify-bulk-optimization .media-item .percent,body[class*="_imagify-ngg-bulk-optimization"] .media-item .percent{position:absolute;top:6px;right:0;text-shadow:none;width:auto;padding:0 5px;line-height:1.85;font-size:14px;font-weight:700;color:#40B1D0}.media_page_imagify-bulk-optimization .media-item .percent,.media_page_imagify-bulk-optimization .media-item .progress,body[class*="_imagify-ngg-bulk-optimization"] .media-item .percent,body[class*="_imagify-ngg-bulk-optimization"] .media-item .progress{text-align:right}.media_page_imagify-bulk-optimization .media-item .progress .bar,body[class*="_imagify-ngg-bulk-optimization"] .media-item .progress .bar{position:relative;width:1px;height:8px;margin-top:0;background:#46B1CE;border-radius:0;-webkit-transition:width .5s;-o-transition:width .5s;transition:width .5s}#imagify-bulk-action{padding:11px 20px}.imagify-columns .col-overview.col-overview{width:calc(100% - 465px);padding-left:20px}.imagify-columns .col-statistics.col-statistics{width:60%}.imagify-columns .col-chart.col-chart{width:40%}@media (max-width:1520px) and (min-width:1381px),(max-width:1086px){.imagify-columns .col-chart.col-chart,.imagify-columns .col-statistics.col-statistics{width:50%}}@media (max-width:1380px) and (min-width:1246px),(max-width:380px){.imagify-overview-chart-container{float:none;margin-right:0}.imagify-doughnut-legend{margin-top:18px}.imagify-global-optim-phrase{padding-top:0;width:auto}}@media (max-width:808px){.imagify-columns .col-chart.col-chart,.imagify-columns .col-statistics.col-statistics{width:auto;float:none;padding:0}.imagify-columns .col-chart.col-chart{margin-top:3em}}.imagify-sep-v{width:1px;background:rgba(255,255,255,.2)}.base-transparent{background:0 0}[class^=imagify-bar-].right-outside-number{-webkit-box-sizing:border-box;box-sizing:border-box;padding-right:4.5em}.right-outside-number .imagify-barnb{display:block;margin-right:-5.25em;text-align:right;font-weight:700;line-height:.8}.imagify-h2-like{margin:0 0 .5em;padding-bottom:.5em;border-bottom:1px solid #E9EFF2;font-size:24px;color:#000;font-weight:700}.imagify-h2-like .dashicons,.imagify-h2-like .dashicons:before{font-size:38px;height:38px;width:38px;margin-right:12px;vertical-align:-5px;color:#40B1D0}.imagify-info-block{position:relative;padding:10px;padding-left:42px;background:#D9E4EB;border-radius:4px;line-height:1.6}.imagify-list-infos{margin:0;padding:0}.imagify-list-infos li{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:15px 5px;text-align:left;font-size:14px;line-height:1.5;color:#626E7B}.imagify-list-infos li:first-child{padding-top:5px}.imagify-list-infos li:last-child{padding-bottom:5px}.imagify-list-infos li+li{border-top:1px solid #E9EFF2}.imagify-info-icon{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-preferred-size:50px;flex-basis:50px}.imagify-info-icon+span{padding-left:20px}.imagify-list-infos a:before{content:'';display:block}.imagify-bulk .imagify-settings-section{border:1px solid #D9D9D9;border-top:0;background:#FFF;color:#4A4A4A}.imagify-bulk h3,.imagify-bulk li,.imagify-bulk p{color:#4A4A4A}.imagify-bulk .imagify-settings-section h3{margin-bottom:2em}.imagify-account-info-col .imagify-options-title{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.imagify-account-info-col p.imagify-meteo-title{margin:0;font-size:24px;font-weight:700;color:#FFF}.imagify-account-info-col .imagify-options-title>a{-ms-flex-preferred-size:100px;flex-basis:100px;margin-left:auto;margin-right:10px;text-decoration:underline;font-size:12px}.imagify-account-info-col .imagify-meteo-title .dashicons,.imagify-account-info-col .imagify-meteo-title .dashicons:before{font-size:38px;width:38px;height:38px;margin-right:4px;color:#40B1D0}.imagify-col-content{background:#F4F7F9;border-left:1px solid #ddd;border-right:1px solid #ddd}.imagify-col-content .imagify-block-secondary{margin-left:-1px;margin-right:-1px}.imagify-col-content .imagify-space-left{margin:15px 30px 15px 0}.imagify-col-content .imagify-space-left p{margin:0 0 10px;font-size:19px;font-weight:500;color:#343A49}.imagify-col-content .imagify-meteo-icon{height:64px;margin:15px 15px 15px 20px}.imagify-col-content .imagify-section-title+p{margin-top:10px}.imagify-account-info-col .imagify-h3-like.imagify-h3-like{color:inherit}.imagify-title .imagify-tooltips{position:absolute;top:100%;left:0}.imagify-tooltips .icon-round{float:left;display:inline-block;width:28px;height:28px;border:1px solid #FFF;margin-right:8px;margin-bottom:8px;font-size:17px;font-style:italic;line-height:29px;font-weight:700;text-align:center;border-radius:50%}.imagify-tooltips .tooltip-content{display:block;position:relative;max-width:250px;padding:7px 15px 8px;background:#2e3242;color:#FFF;font-size:10px;border-radius:3px}.imagify-tooltips.right .tooltip-content{margin-left:12px}.imagify-tooltips.bottom .tooltip-content{margin-top:4px}.imagify-inline-options label .tooltip-content{position:absolute;left:0;right:0;top:100%;text-transform:none;font-size:10px;letter-spacing:0;text-align:center}.imagify-tooltips .tooltip-content:after{content:"";position:absolute}.imagify-tooltips.right .tooltip-content:after{top:16px;left:-6px;border-right:8px solid #2e3242;border-top:6px solid transparent;border-bottom:6px solid transparent}.imagify-tooltips.bottom .tooltip-content:after{top:-5px;left:50%;margin-left:-3px;border-bottom:6px solid #2e3242;border-left:6px solid transparent;border-right:6px solid transparent}.imagify-space-tooltips .tooltip-content{max-width:280px;margin-top:20px;margin-left:0;padding:5px 15px;font-size:13px;background:#40B1D0;-webkit-box-shadow:0 3px 0 #338EA6;box-shadow:0 3px 0 #338EA6}.imagify-space-tooltips .tooltip-content:after{top:-14px;left:50%;margin-left:-7px;border:0;border-bottom:15px solid #40B1D0;border-left:15px solid transparent;border-right:15px solid transparent}.tooltip-content.tooltip-table{display:table;width:100%}.tooltip-content.tooltip-table>*{display:table-cell;vertical-align:middle}.tooltip-content .cell-icon{width:28px}.tooltip-content .cell-icon .icon{margin-bottom:0}.tooltip-content .cell-text{padding:5px 10px 5px 0;line-height:1.3}.tooltip-content .cell-sep{width:1px;background:rgba(255,255,255,.4)}.tooltip-content .cell-cta{padding-left:10px}.tooltip-content .cell-cta a{display:block;color:#FFF;width:100%;height:100%;white-space:nowrap}.imagify-number-you-optimized{margin-bottom:1.35em;overflow:hidden}.imagify-number-you-optimized .number{display:table-cell;padding-right:15px;font-size:48px;font-weight:700;line-height:1;vertical-align:middle;white-space:nowrap;color:#000}.imagify-number-you-optimized [id=imagify-total-optimized-attachments-pct]{color:#40B1D0}.imagify-number-you-optimized .text{display:table-cell;vertical-align:middle;overflow:hidden;font-size:12px;color:#626E7B}.imagify-number-you-optimized>p{display:table}.imagify-bars{padding-right:15px}.imagify-bars p{font-size:12px;margin-bottom:5px}.imagify-bars+.imagify-number-you-optimized{border-bottom:0;padding-top:.85em}.imagify-bars+.imagify-number-you-optimized p{color:#46b1ce}.imagify-bulk-table{margin-top:2em}.imagify-table-header{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:15px 25px;background:#343A49;color:#FFF}.imagify-newbie{margin-top:4em;position:relative;overflow:visible}.imagify-newbie .imagify-new-feature.imagify-new-feature{position:absolute;top:0;left:25px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);margin:0;padding:8px 20px;font-size:14px;letter-spacing:.02em;text-transform:uppercase;font-weight:700;color:#FFF;background:#8BC34A}.imagify-newbie .imagify-table-header{padding:30px 25px;border:2px solid #8BC34A;background:#F3F9EC}.imagify-th-titles .dashicons,.imagify-th-titles .dashicons:before{width:38px;height:38px;margin-right:20px;font-size:38px;color:#40B1D0}.imagify-newbie .imagify-th-titles .dashicons:before{color:#8BC34A}.imagify-th-title.imagify-th-title.imagify-th-title{margin:0;font-size:24px;font-weight:500;color:#FFF}.imagify-newbie .imagify-th-title.imagify-th-title{color:#343A49}.imagify-th-subtitle.imagify-th-subtitle.imagify-th-subtitle{margin:0 0 5px;font-size:14px;color:#7A8996;font-weight:500}.imagify-th-action .imagify-button-clean{font-size:12px;color:#7A8996}.imagify-th-action .button:focus,.imagify-th-action .button:hover,.imagify-th-action .imagify-is-active{color:#FFF}.imagify-bulk-table table{width:100%;border-spacing:0;border-collapse:collapse}.imagify-bulk-table td{padding:20px}.imagify-bulk-table-details{border-bottom:2px solid #E5EBEF}.imagify-bulk-table-details thead th,.imagify-bulk-table-details thead tr{background:#4A5362}.imagify-bulk-table-details thead th{padding:12px 20px;text-align:left;font-weight:700;color:#E5EBEF;font-size:12px}.imagify-bulk-table-details tbody tr:nth-child(odd) td{background:#F2F5F7}.imagify-bulk-table-content{border:1px solid #D3D3D3;border-top:0}.imagify-bulk-table-footer{padding:20px;color:#626E7B;background:#F2F5F7}.imagify-bulk-table tbody tr+tr{border-top:3px solid #F2F5F7}.imagify-bulk-table tbody td,.imagify-bulk-table tbody tr{background:#FFF}.imagify-bulk-table .imagify-row-progress{display:none;height:8px;padding:0}.imagify-bulk-table .imagify-no-uploaded-yet td{height:200px;font-size:17px;letter-spacing:.1em;word-spacing:.12em;vertical-align:middle;text-transform:uppercase;font-weight:700;text-align:center;color:#999;background-color:#FFF}.imagify-selector{position:relative}.imagify-selector-list{background:#FFF;border:1px solid #F4F7F9;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.1);box-shadow:0 6px 12px rgba(0,0,0,.1);border-radius:3px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}.imagify-selector-list li:first-child label{border-radius:3px 3px 0 0}.imagify-selector-list li:last-child label{border-radius:0 0 3px 3px}.imagify-selector-list li{margin:0}.imagify-selector-list li+li{border-top:1px solid #F4F7F9}.imagify-selector-list svg{margin-right:5px}.imagify-selector-list .imagify-selector-current-value label,.imagify-selector-list input:checked+label{background:#343A49;color:#FFF}.imagify-selector-list .imagify-selector-current-value input:focus+label,.imagify-selector-list .imagify-selector-current-value label:hover,.imagify-selector-list input:checked+label:hover,.imagify-selector-list input:focus+label,.imagify-selector-list label:hover{background:#40B1D0;color:#F4F7F9}.imagify-selector-list .imagify-selector-current-value input:focus+label polygon,.imagify-selector-list .imagify-selector-current-value label:hover polygon,.imagify-selector-list input:checked+label:hover polygon,.imagify-selector-list input:focus+label polygon,.imagify-selector-list label:hover polygon{fill:#FFF}.imagify-selector-list .imagify-selector-current-value input:focus+label polygon[fill="#CCD1D6"],.imagify-selector-list .imagify-selector-current-value label:hover polygon[fill="#CCD1D6"],.imagify-selector-list input:checked+label:hover polygon[fill="#CCD1D6"],.imagify-selector-list input:focus+label polygon[fill="#CCD1D6"],.imagify-selector-list label:hover polygon[fill="#CCD1D6"]{fill:#3694AE}.imagify-selector-list li label{display:block;padding:10px;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-selector-list polygon{-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-selector-list{position:absolute;top:0;left:0;right:0;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.imagify-selector-list[aria-hidden=true]{opacity:0;visibility:hidden;-webkit-transform:translateY(-50%) scale(0);-ms-transform:translateY(-50%) scale(0);transform:translateY(-50%) scale(0)}.imagify-selector-list[aria-hidden=false]{opacity:1;visibility:visible;-webkit-transform:translateY(-50%) scale(1);-ms-transform:translateY(-50%) scale(1);transform:translateY(-50%) scale(1)}.button .imagify-selector-current-value-info{position:relative;padding-right:20px}.button .imagify-selector-current-value-info:after{content:'';position:absolute;right:0;top:50%;margin-top:-3px;border-top:6px solid #7A8996;border-left:6px solid transparent;border-right:6px solid transparent}.imagify-row-complete{padding:35px 20px;margin-top:2em;background:#8BC34A;color:#FFF;text-shadow:0 0 2px rgba(0,0,0,.1)}.imagify-row-complete .imagify-ac-chart{margin-top:3px}.imagify-row-complete.imagify-row-complete p{color:#FFF;margin:0}@-webkit-keyframes congrate{0%{opacity:0;-webkit-transform:scale(1)}50%{-webkit-transform:scale(1.05);opacity:1}100%{-webkit-transform:scale(1);opacity:1}}@keyframes congrate{0%{opacity:0;-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(1.05);transform:scale(1.05);opacity:1}100%{-webkit-transform:scale(1);transform:scale(1);opacity:1}}.imagify-row-complete.done{-webkit-animation:congrate .5s ease-in-out;animation:congrate .5s ease-in-out}.imagify-all-complete{margin:1.5em 0}.imagify-all-complete>div{display:inline-block;vertical-align:middle}.imagify-ac-report{min-width:310px;margin-right:20px}.imagify-ac-chart{width:46px;height:46px;float:left;margin:0 20px 0 10px}.imagify-ac-report-text{overflow:hidden}.imagify-ac-report-text p{line-height:1.3}.imagify-ac-rt-big{font-weight:700;font-size:24px;letter-spacing:.15em;word-spacing:.15em;text-transform:uppercase}.imagify-ac-share{text-align:right}.imagify-ac-share-content{display:inline-block;padding:10px 15px;background:rgba(255,255,255,.2)}.imagify-ac-share-content>*{display:inline-block;vertical-align:middle}.imagify-bulk-table .imagify-ac-share-content p{margin-right:5px}.imagify-share-networks,.imagify-share-networks li{margin:0}.imagify-share-networks li{display:inline-block}.imagify-share-networks a{display:inline-block;vertical-align:-7px;margin:0 5px;text-decoration:none;color:#FFF}.imagify-cell-checkbox{width:35px}.imagify-cell-checkbox p{margin:0}.imagify-cell-checkbox-loader{display:block;width:27px;height:28px;line-height:0;-webkit-animation:loading 4s infinite linear;animation:loading 4s infinite linear}@-webkit-keyframes loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.imagify-cell-checkbox-loader.hidden{display:none;-webkit-animation:none;animation:none}.imagify-cell-label,.imagify-cell-title label{font-size:14px;text-transform:uppercase;letter-spacing:.02em;font-weight:700}.imagify-cell-label{margin-right:10px}.imagify-cell-value{font-size:12px;font-weight:500;color:#7A8996}td.imagify-cell-title{padding-left:0}.imagify-cell-original-size .imagify-cell-label,.imagify-cell-title label{color:#1F2332}.imagify-cell-optimized-size,.imagify-cell-original-size{font-weight:500;color:#7A8996;font-size:12px}.imagify-cell-optimized-size .imagify-cell-label{color:#338EA6}.imagify-cell-count-optimized{font-size:14px;font-weight:700;color:#338EA6}.imagify-cell-count-errors{color:#C51162;font-weight:700;font-size:14px}.imagify-cell-count-errors a{margin-left:5px;color:#7A8996;font-weight:400;font-size:12px}.imagify-cell-filename{max-width:200px}.imagify-cell-status{max-width:145px}.imagify-cell-status .dashicons-warning{margin-right:2px}.imagistatus .dashicons,.imagiuploaded{margin-right:5px}.imagify-cell-thumbnails{max-width:120px}td.imagify-cell-filename{-o-text-overflow:clip;text-overflow:clip;white-space:nowrap;overflow:hidden}.imagify-bulk-table .imagify-cell-thumbnails{text-align:center}.imagify-cell-percentage,.imagify-cell-savings{color:#46B1CE;font-weight:700}.imagify-bulk-table td.imagify-cell-totaloriginal{padding-right:78px}.imagify-cell-totaloriginal{text-align:right}.imagify-cell-level{width:145px}.imagify-selector-button.imagify-selector-button{border:1px solid #FFF;padding:2px 10px}.imagify-selector-button.imagify-selector-button:focus,.imagify-selector-button.imagify-selector-button:hover{border-color:#EEE;-webkit-box-shadow:0 4px 8px rgba(0,0,0,.1);box-shadow:0 4px 8px rgba(0,0,0,.1)}.imagifilename,.imagiuploaded{display:inline-block;vertical-align:middle;margin-left:5px;color:#626E7B;font-weight:500}.imagifilename{font-size:12px}.imagiuploaded{width:33px;height:33px;margin-left:-8px;overflow:hidden;background:url(../images/upload-image.png) no-repeat;background-size:cover}.imagiuploaded img{max-width:100%;height:auto}.imagistatus{color:#8CA6B3;text-transform:uppercase;font-weight:700}.status-compressing{color:#46B1CE}.status-error{color:#CE0B24}.status-warning{color:#f5a623}.status-complete{color:#8CC152}.imagify-bulk-submit{padding:15px 0 8px}.imagify-settings .button-primary.button-primary[disabled]{color:#4A4A4A!important;background:#D9E4EB!important;text-shadow:none!important;cursor:not-allowed}.dashicons.rotate{-webkit-animation:icon-rotate 2.6s infinite linear;animation:icon-rotate 2.6s infinite linear}.imagify-cell-status .dashicons-admin-generic{-webkit-transform-origin:48.75% 51.75%;-ms-transform-origin:48.75% 51.75%;transform-origin:48.75% 51.75%}@-webkit-keyframes icon-rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes icon-rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.imagify-col.imagify-col.imagify-account-info-col{width:465px}@media (max-width:1245px){.imagify-col.imagify-col.imagify-account-info-col{width:auto;max-width:none;float:none}.imagify-columns .col-overview.col-overview{float:none;width:auto;padding-left:0;padding-right:0}}@media (max-width:1200px){.imagify-settings .imagify-title .imagify-logo{display:none}}@media (max-width:940px){.imagify-bulk-table-container tbody,.imagify-bulk-table-container tr{text-align:left}.imagify-bulk-table-container tbody,.imagify-bulk-table-container tbody td,.imagify-bulk-table-container tbody tr{display:block}.imagify-bulk-table-container tbody td{padding:20px}.imagify-cell-checkbox,.imagify-cell-title{float:left}.imagify-cell-checkbox{width:26px}.imagify-bulk-table-container .imagify-cell-title{padding-left:10px;width:calc(100% - 96px)}.imagify-cell-count-optimized:before,.imagify-cell-title:after{content:'';display:table;clear:both;width:100%}.imagify-cell-count-optimized{clear:both}.imagify-bulk-table-container .imagify-cell-title~td{display:inline-block}.imagify-bulk-table-container td.imagify-cell-level{display:block}}@media (max-width:918px){.imagify-settings .imagify-title{display:block}.imagify-settings .imagify-documentation-link-box{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}}.hidden{display:none}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/files-list.css b/wp-content/plugins/imagify/assets/css/files-list.css
new file mode 100644
index 00000000..b84a0a87
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/files-list.css
@@ -0,0 +1,162 @@
+/* Filter block */
+.imagify-files-list .wp-filter {
+ padding: 15px 20px;
+}
+.imagify-files-list .filter-items select {
+ height: auto;
+ padding: 6px;
+ margin-right: 12px;
+}
+.imagify-files-list .filter-items .button {
+ height: auto;
+ padding: 2px 12px 3px;
+}
+
+/* Empty table */
+.imagify-files-list .no-items td {
+ padding: 35px;
+ text-align: center;
+ font-size: 18px;
+}
+.imagify-files-list .no-items td a {
+ text-decoration: underline;
+}
+
+/* Th sortable */
+.imagify-files-list .sortable a {
+ color: #000;
+}
+
+/* Global links */
+.imagify-files-list a {
+ color: #3694AE;
+}
+
+/* Global TDs */
+.imagify-files-list tbody td,
+.imagify-files-list tbody th,
+.imagify-files-list.imagify-files-list tbody .check-column {
+ vertical-align: middle;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ color: #626E7B;
+}
+
+/* Col Title */
+.imagify-files-list .column-title strong {
+ font-weight: normal;
+ font-size: 14px;
+}
+.imagify-files-list .column-title strong a {
+ display: inline-flex;
+ align-items: center;
+ word-break: break-all;
+ word-wrap: break-word;
+ font-weight: normal;
+}
+.imagify-files-list .filename {
+ font-size: 12px;
+ font-weight: bold;
+}
+.imagify-files-list .media-icon {
+ position: relative;
+ width: 60px;
+ overflow: hidden;
+ flex-shrink: 0;
+}
+.media-icon .centered {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ transform: translate( 50%, 50% );
+}
+.media-icon .centered img {
+ position: absolute;
+ left: 0;
+ top: 0;
+ transform: translate( -50%, -50% );
+}
+table.media .column-title .media-icon.landscape img {
+ max-width: none;
+ width: auto;
+ height: 60px;
+}
+table.media .column-title .media-icon.portrait img {
+ width: 60px;
+}
+
+/* Optimization datas Col */
+.imagify-files-list ul.imagify-datas-list {
+ font-size: 11px;
+}
+.imagify-files-list ul.imagify-datas-list .big {
+ font-size: 13px;
+}
+.imagify-files-list ul.imagify-datas-list span.imagify-chart-value {
+ font-size: 12px;
+}
+.imagify-files-list ul.imagify-datas-list .imagify-chart-container {
+ margin-right: 2px;
+}
+.imagify-files-list ul.imagify-datas-list canvas {
+ width: 18px!important;
+ height: 18px!important;
+}
+
+/* Optimization Level Col */
+.imagify-files-list .optimization_level {
+ text-align: center;
+ font-weight: bold;
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 0.02em;
+}
+.imagify-files-list .column-optimization_level,
+.imagify-files-list .column-optimization_level a {
+ text-align: center;
+}
+.imagify-files-list .column-optimization_level a span {
+ float: none;
+ display: inline-block;
+ vertical-align: middle;
+}
+.imagify-files-list .column-optimization_level .sorting-indicator {
+ vertical-align: -10px;
+}
+
+/* Actions col */
+.imagify-files-list .column-actions .button,
+.imagify-files-list .column-actions .button-primary {
+ padding: 5px 20px;
+ font-size: 14px;
+ height: auto;
+}
+.imagify-files-list .column-actions .button-primary {
+ background: #3694AE;
+ color: #FFF;
+ border: 0;
+ box-shadow: none;
+ text-shadow: none;
+}
+.imagify-files-list .column-actions a,
+.status a.button-imagify-refresh-status {
+ display: inline-block;
+ margin: .3em 0;
+ font-size: 12px;
+ font-weight: bold;
+}
+.imagify-files-list .imagify-status-already_optimized {
+ font-weight: bold;
+ color: #8BC34A;
+}
+.imagify-files-list .column-actions a .dashicons,
+.imagify-files-list .column-actions a .dashicons:before,
+.status a.button-imagify-refresh-status .dashicons,
+.status a.button-imagify-refresh-status .dashicons:before {
+ margin-right: 2px;
+ font-size: 17px;
+ height: 17px;
+ width: 17px;
+}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/files-list.min.css b/wp-content/plugins/imagify/assets/css/files-list.min.css
new file mode 100644
index 00000000..17b61452
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/files-list.min.css
@@ -0,0 +1 @@
+.imagify-files-list .wp-filter{padding:15px 20px}.imagify-files-list .filter-items select{height:auto;padding:6px;margin-right:12px}.imagify-files-list .filter-items .button{height:auto;padding:2px 12px 3px}.imagify-files-list .no-items td{padding:35px;text-align:center;font-size:18px}.imagify-files-list .no-items td a{text-decoration:underline}.imagify-files-list .sortable a{color:#000}.imagify-files-list a{color:#3694AE}.imagify-files-list tbody td,.imagify-files-list tbody th,.imagify-files-list.imagify-files-list tbody .check-column{vertical-align:middle;padding-top:20px;padding-bottom:20px;color:#626E7B}.imagify-files-list .column-title strong{font-weight:400;font-size:14px}.imagify-files-list .column-title strong a{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;word-break:break-all;word-wrap:break-word;font-weight:400}.imagify-files-list .filename{font-size:12px;font-weight:700}.imagify-files-list .media-icon{position:relative;width:60px;overflow:hidden;-ms-flex-negative:0;flex-shrink:0}.media-icon .centered{position:absolute;left:0;top:0;width:100%;height:100%;-webkit-transform:translate(50%,50%);-ms-transform:translate(50%,50%);transform:translate(50%,50%)}.media-icon .centered img{position:absolute;left:0;top:0;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}table.media .column-title .media-icon.landscape img{max-width:none;width:auto;height:60px}table.media .column-title .media-icon.portrait img{width:60px}.imagify-files-list ul.imagify-datas-list{font-size:11px}.imagify-files-list ul.imagify-datas-list .big{font-size:13px}.imagify-files-list ul.imagify-datas-list span.imagify-chart-value{font-size:12px}.imagify-files-list ul.imagify-datas-list .imagify-chart-container{margin-right:2px}.imagify-files-list ul.imagify-datas-list canvas{width:18px!important;height:18px!important}.imagify-files-list .optimization_level{text-align:center;font-weight:700;font-size:14px;text-transform:uppercase;letter-spacing:.02em}.imagify-files-list .column-optimization_level,.imagify-files-list .column-optimization_level a{text-align:center}.imagify-files-list .column-optimization_level a span{float:none;display:inline-block;vertical-align:middle}.imagify-files-list .column-optimization_level .sorting-indicator{vertical-align:-10px}.imagify-files-list .column-actions .button,.imagify-files-list .column-actions .button-primary{padding:5px 20px;font-size:14px;height:auto}.imagify-files-list .column-actions .button-primary{background:#3694AE;color:#FFF;border:0;-webkit-box-shadow:none;box-shadow:none;text-shadow:none}.imagify-files-list .column-actions a,.status a.button-imagify-refresh-status{display:inline-block;margin:.3em 0;font-size:12px;font-weight:700}.imagify-files-list .imagify-status-already_optimized{font-weight:700;color:#8BC34A}.imagify-files-list .column-actions a .dashicons,.imagify-files-list .column-actions a .dashicons:before,.status a.button-imagify-refresh-status .dashicons,.status a.button-imagify-refresh-status .dashicons:before{margin-right:2px;font-size:17px;height:17px;width:17px}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/notices.css b/wp-content/plugins/imagify/assets/css/notices.css
new file mode 100644
index 00000000..70db2ad8
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/notices.css
@@ -0,0 +1,304 @@
+/* Error Notice */
+.imagify-plugins-error {
+ overflow: hidden;
+ padding-left: 20px;
+ list-style-type: disc
+}
+.imagify-plugins-error li {
+ width:300px;
+ line-height:30px
+}
+@media (max-width: 570px) {
+ .imagify-plugins-error li {
+ width: auto;
+ }
+}
+
+/* Notice close link */
+.imagify-notice-dismiss.notice-dismiss {
+ text-decoration: none;
+}
+
+/* Notices in Imagify related pages */
+.media_page_imagify-bulk-optimization .notice,
+body[class*="_imagify-ngg-bulk-optimization"] .notice,
+.settings_page_imagify .notice {
+ margin-right: 20px;
+ margin-left: 2px;
+}
+
+.imagify-notice .button-mini {
+ padding: 2px 10px;
+ font-size: 13px;
+}
+
+.imagify-notice.imagify-notice {
+ position: relative;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ padding: 0;
+ margin: 10px 20px 10px 2px;
+ border: 0 none;
+ background: #4A5362;
+ box-shadow: none;
+ color: #FFF;
+}
+@media (max-width:782px) {
+ .imagify-notice.imagify-notice,
+ .imagify-welcome {
+ margin-right: 12px;
+ }
+}
+@media (max-width: 450px) {
+ .imagify-notice.imagify-notice {
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ }
+}
+.wrap .imagify-notice {
+ margin: 5px 15px 2px;
+ position: relative;
+}
+.imagify-notice-logo {
+ padding: 18px 23px;
+ background: #40B1D0;
+}
+.imagify-notice-logo .imagify-logo {
+ opacity: 1;
+}
+.imagify-flex-notice-content .imagify-notice-logo {
+ display: flex;
+ align-items: center;
+}
+.updated .imagify-notice-logo {
+ background: #8BC34A;
+}
+.error .imagify-notice-logo {
+ background: #C51162;
+}
+
+.imagify-notice-title {
+ font-size: 15px;
+}
+
+.imagify-notice-content {
+ padding: 5px 23px;
+}
+.imagify-notice-content.imagify-notice-content p {
+ margin: 0.65em 0;
+}
+.imagify-flex-notice-content .imagify-notice-content {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0;
+}
+.imagify-flex-notice-content .imagify-notice-content > * {
+ padding: 10px 20px;
+}
+.imagify-flex-notice-content .imagify-meteo-icon img {
+ height: 100%;
+ margin-top: 6px;
+}
+.imagify-notice-quota [class^="imagify-bar-"] {
+ background: #1F2332;
+}
+.imagify-notice-quota .imagify-space-left p {
+ margin: 0;
+}
+.imagify-flex-notice-content .imagify-notice-content .imagify-notice-quota {
+ padding-right: 24px;
+ padding-left: 8px;
+ background: #343A49;
+}
+.imagify-notice a {
+ color: #40B1D0;
+}
+.imagify-notice a:hover,
+.imagify-notice a:focus {
+ color: #FEE102;
+}
+
+.imagify-notice code {
+ background: rgba(0, 0, 0, 0.4) none repeat scroll 0 0;
+}
+
+.imagify-notice .imagify-rate-us.imagify-rate-us {
+ text-align: left;
+}
+
+.imagify-notice .imagify-rate-us .stars {
+ margin: 0;
+}
+
+/**
+ * == Welcome section
+ */
+.imagify-welcome {
+ margin: 30px 20px 0 0;
+}
+.imagify-welcome .baseline {
+ display: inline-block;
+ margin: .2em 0 0 2em;
+ font-size: 17px;
+}
+.imagify-welcome .imagify-logo {
+ vertical-align: middle;
+}
+.imagify-welcome-remove {
+ position: absolute;
+ top: 50%; right: 15px;
+ margin-top: -8px;
+ color: #FFF;
+ text-decoration: none;
+}
+/* Welcome columns */
+.imagify-columns [class^="col-"] img {
+ float: left;
+ margin-right: 18px;
+}
+.imagify-col-content {
+ overflow: hidden;
+}
+.imagify-col-title {
+ margin: 0 0 15px 0;
+ font-size: 23px;
+}
+.counter .imagify-col-title:before {
+ counter-increment: cols;
+ content: counter(cols) ". ";
+ color: #40B1D0;
+}
+.imagify-col-desc {
+ color: #5F758E;
+ margin-bottom: 2em;
+}
+
+/* WP Rocket notice */
+.imagify-rkt-notice.imagify-rkt-notice {
+ position: relative;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ -ms-grid-row-align: center;
+ align-items: center;
+ padding: 10px 45px 10px 0;
+ border: 0 none;
+ box-shadow: none;
+ color: #FFF;
+ background: #1F2332;
+}
+.media_page_imagify-bulk-optimization .imagify-rkt-notice {
+ margin-left: 2px;
+ margin-right: 20px;
+}
+@media (max-width: 782px) {
+ .media_page_imagify-bulk-optimization .imagify-rkt-notice {
+ margin-left: 0;
+ margin-right: 12px;
+ }
+}
+.imagify-rkt-notice .imagify-cross {
+ position: absolute;
+ right: 8px; top: 50%;
+ width: 22px; height: 22px;
+ padding: 0;
+ margin-top: -11px;
+ background: transparent;
+ color: rgba(255,255,255,.5);
+ text-decoration: none;
+ border-radius: 50%;
+ transition: all .275s;
+}
+.imagify-rkt-notice .imagify-cross .dashicons {
+ position: relative;
+ top: 2px; left: 1px;
+ transition: all .275s;
+}
+.imagify-rkt-notice .imagify-cross:hover {
+ background: #FFF;
+}
+.imagify-rkt-notice .imagify-cross:hover .dashicons {
+ color: #412355;
+}
+.imagify-rkt-notice .imagify-rkt-cta,
+.imagify-rkt-notice .imagify-rkt-logo,
+.imagify-rkt-notice .imagify-rkt-coupon {
+ -webkit-flex-shrink: 0;
+ -ms-flex-negative: 0;
+ flex-shrink: 0;
+}
+.imagify-rkt-notice .imagify-rkt-logo {
+ width: 150px!important; /* !important because of a dirtugly WP Engine code */
+ text-align: center;
+ padding: 0 25px 0 30px;
+ line-height: 0.8;
+}
+.imagify-rkt-notice .imagify-rkt-msg {
+ width: 100%!important; /* !important because of a dirtugly WP Engine code */
+ color: #FFF;
+ padding: 0 15px;
+ font-size: 14px;
+ line-height: 1.6;
+}
+.imagify-rkt-notice .imagify-rkt-coupon {
+ width: 150px!important; /* !important because of a dirtugly WP Engine code */
+ padding: 0 15px;
+}
+.imagify-rkt-notice .imagify-rkt-coupon-code {
+ padding: 5px 10px;
+ font-size: 23px;
+ font-weight: bold;
+ border: 1px dashed #F56640;
+ color: #F56640;
+}
+.imagify-rkt-notice .imagify-rkt-cta {
+ width: 250px!important; /* !important because of a dirtugly WP Engine code */
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ -webkit-flex-basis: 200px;
+ -ms-flex-preferred-size: 200px;
+ flex-basis: 200px;
+}
+.imagify-rkt-notice .button.button {
+ position: relative;
+ top: -1px;
+ height: auto;
+ font-weight: 600;
+ font-size: 14px;
+ border: 0 none;
+ padding: 9px 18px 9px;
+ background: #F56640;
+ box-shadow: none;
+ text-shadow: none!important;
+}
+@media (max-width: 880px) {
+ .imagify-rkt-notice {
+ -webkit-flex-wrap: wrap;
+ -ms-flex-wrap: wrap;
+ flex-wrap: wrap;
+ }
+ .imagify-rkt-notice .imagify-rkt-msg,
+ .imagify-rkt-notice .imagify-rkt-cta,
+ .imagify-rkt-notice .imagify-rkt-logo {
+ text-align: left;
+ padding: 5px 15px;
+ }
+ .imagify-cross.imagify-cross {
+ top: 8px;
+ margin-top: 0;
+ }
+ .imagify-rkt-notice .imagify-cross .dashicons {
+ top: 1px;
+ }
+}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/notices.min.css b/wp-content/plugins/imagify/assets/css/notices.min.css
new file mode 100644
index 00000000..b29449dd
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/notices.min.css
@@ -0,0 +1 @@
+.imagify-plugins-error{overflow:hidden;padding-left:20px;list-style-type:disc}.imagify-plugins-error li{width:300px;line-height:30px}@media (max-width:570px){.imagify-plugins-error li{width:auto}}.imagify-notice-dismiss.notice-dismiss{text-decoration:none}.media_page_imagify-bulk-optimization .notice,.settings_page_imagify .notice,body[class*="_imagify-ngg-bulk-optimization"] .notice{margin-right:20px;margin-left:2px}.imagify-notice .button-mini{padding:2px 10px;font-size:13px}.imagify-notice.imagify-notice{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;padding:0;margin:10px 20px 10px 2px;border:0;background:#4A5362;-webkit-box-shadow:none;box-shadow:none;color:#FFF}@media (max-width:782px){.imagify-notice.imagify-notice,.imagify-welcome{margin-right:12px}}@media (max-width:450px){.imagify-notice.imagify-notice{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}.wrap .imagify-notice{margin:5px 15px 2px;position:relative}.imagify-notice-logo{padding:18px 23px;background:#40B1D0}.imagify-notice-logo .imagify-logo{opacity:1}.imagify-flex-notice-content .imagify-notice-logo{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.updated .imagify-notice-logo{background:#8BC34A}.error .imagify-notice-logo{background:#C51162}.imagify-notice-title{font-size:15px}.imagify-notice-content{padding:5px 23px}.imagify-notice-content.imagify-notice-content p{margin:.65em 0}.imagify-flex-notice-content .imagify-notice-content{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0}.imagify-flex-notice-content .imagify-notice-content>*{padding:10px 20px}.imagify-flex-notice-content .imagify-meteo-icon img{height:100%;margin-top:6px}.imagify-notice .imagify-rate-us .stars,.imagify-notice-quota .imagify-space-left p{margin:0}.imagify-notice-quota [class^=imagify-bar-]{background:#1F2332}.imagify-flex-notice-content .imagify-notice-content .imagify-notice-quota{padding-right:24px;padding-left:8px;background:#343A49}.imagify-notice a{color:#40B1D0}.imagify-notice a:focus,.imagify-notice a:hover{color:#FEE102}.imagify-notice code{background:rgba(0,0,0,.4)}.imagify-notice .imagify-rate-us.imagify-rate-us{text-align:left}.imagify-welcome{margin:30px 20px 0 0}.imagify-welcome .baseline{display:inline-block;margin:.2em 0 0 2em;font-size:17px}.imagify-welcome .imagify-logo{vertical-align:middle}.imagify-welcome-remove{position:absolute;top:50%;right:15px;margin-top:-8px;color:#FFF;text-decoration:none}.imagify-columns [class^=col-] img{float:left;margin-right:18px}.imagify-col-content{overflow:hidden}.imagify-col-title{margin:0 0 15px;font-size:23px}.counter .imagify-col-title:before{counter-increment:cols;content:counter(cols) ". ";color:#40B1D0}.imagify-col-desc{color:#5F758E;margin-bottom:2em}.imagify-rkt-notice.imagify-rkt-notice{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;padding:10px 45px 10px 0;border:0;-webkit-box-shadow:none;box-shadow:none;color:#FFF;background:#1F2332}.media_page_imagify-bulk-optimization .imagify-rkt-notice{margin-left:2px;margin-right:20px}@media (max-width:782px){.media_page_imagify-bulk-optimization .imagify-rkt-notice{margin-left:0;margin-right:12px}}.imagify-rkt-notice .imagify-cross{position:absolute;right:8px;top:50%;width:22px;height:22px;padding:0;margin-top:-11px;background:0 0;color:rgba(255,255,255,.5);text-decoration:none;border-radius:50%;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-rkt-notice .imagify-cross .dashicons{position:relative;top:2px;left:1px;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-rkt-notice .imagify-cross:hover{background:#FFF}.imagify-rkt-notice .imagify-cross:hover .dashicons{color:#412355}.imagify-rkt-notice .imagify-rkt-coupon,.imagify-rkt-notice .imagify-rkt-cta,.imagify-rkt-notice .imagify-rkt-logo{-ms-flex-negative:0;flex-shrink:0}.imagify-rkt-notice .imagify-rkt-logo{width:150px!important;text-align:center;padding:0 25px 0 30px;line-height:.8}.imagify-rkt-notice .imagify-rkt-msg{width:100%!important;color:#FFF;padding:0 15px;font-size:14px;line-height:1.6}.imagify-rkt-notice .imagify-rkt-coupon{width:150px!important;padding:0 15px}.imagify-rkt-notice .imagify-rkt-coupon-code{padding:5px 10px;font-size:23px;font-weight:700;border:1px dashed #F56640;color:#F56640}.imagify-rkt-notice .imagify-rkt-cta{width:250px!important;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:200px;flex-basis:200px}.imagify-rkt-notice .button.button{position:relative;top:-1px;height:auto;font-weight:600;font-size:14px;border:0;padding:9px 18px;background:#F56640;-webkit-box-shadow:none;box-shadow:none;text-shadow:none!important}@media (max-width:880px){.imagify-rkt-notice{-ms-flex-wrap:wrap;flex-wrap:wrap}.imagify-rkt-notice .imagify-rkt-cta,.imagify-rkt-notice .imagify-rkt-logo,.imagify-rkt-notice .imagify-rkt-msg{text-align:left;padding:5px 15px}.imagify-cross.imagify-cross{top:8px;margin-top:0}.imagify-rkt-notice .imagify-cross .dashicons{top:1px}}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/options.css b/wp-content/plugins/imagify/assets/css/options.css
new file mode 100644
index 00000000..ea65f0ac
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/options.css
@@ -0,0 +1,902 @@
+.wrap.imagify-settings {
+ margin-right: 0;
+}
+.imagify-settings.imagify-have-rocket {
+ margin-right: 20px;
+}
+
+/* The field that checks the API Key */
+#imagify-check-api-container {
+ display: block;
+ margin-top: 6px;
+ font-weight: bold;
+}
+
+#imagify-check-api-container .dashicons {
+ font-size: 25px;
+}
+
+#imagify-check-api-container .dashicons-no:before {
+ color: #f06e57;
+ vertical-align: -1px;
+}
+
+#imagify-check-api-container .imagify-icon {
+ font-size: 1.8em;
+ margin-right: 3px;
+ margin-left: 1px;
+ color: #8BC34A;
+ vertical-align: -2px;
+}
+
+.imagify-account-info-col .imagify-api-line {
+ padding: 22px 26px;
+ background: #343A49
+}
+
+.imagify-api-line label,
+p.imagify-api-key-invite-title {
+ display: block;
+ margin-bottom: 6px;
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 0.02em;
+ font-weight: bold;
+ color: #343A49;
+}
+.imagify-account-info-col .imagify-api-line label {
+ color: #E5EBEF;
+ display: inline-block;
+}
+.imagify-api-line.imagify-api-line input[type="text"] {
+ width: 100%;
+ padding: 6px 10px;
+ border: 1px solid #40B1D0;
+ font-family: "PT Mono", "Consolas", monospace;
+ font-size: 14px;
+ letter-spacing: 0.01em;
+ font-weight: bold;
+ color: #40B1D0;
+ background: transparent;
+ box-shadow: none;
+}
+
+.imagify-no-api-key .imagify-api-line {
+ margin: 3em 0 0 0;
+ padding: 2em 0 0;
+ border-top: 1px solid #D5D6D9;
+}
+.imagify-no-api-key .imagify-api-line input[type="text"] {
+ margin-top: 5px;
+ width: 400px;
+ max-width: 100%;
+}
+.imagify-settings .imagify-no-api-key div.submit.submit {
+ border: 0;
+ padding: 0 16px;
+ margin-top: 0;
+ background: #FFF;
+}
+.imagify-settings .imagify-no-api-key div.submit.submit p {
+ padding-bottom: 0;
+}
+
+.imagify-options-title {
+ margin: .75em 0 0 0;
+ font-size: 24px;
+ letter-spacing: 0.02em;
+ font-weight: bold;
+ color: #343A49;
+}
+
+.imagify-options-subtitle {
+ padding-bottom: .3em;
+ margin-bottom: 20px;
+ border-bottom: 1px solid #D2D3D6;
+ font-size: 14px;
+ letter-spacing: 0.01em;
+ font-weight: bold;
+ text-transform: uppercase;
+ color: #626E7B;
+}
+
+.imagify-options-subtitle a {
+ font-size: 12px;
+ color: #338EA6;
+ text-transform: none;
+ letter-spacing: 0;
+}
+
+.imagify-options-subtitle .imagify-info {
+ margin-left: 15px;
+}
+
+.imagify-setting-line {
+ border-top: 1px solid #D2D3D6;
+ padding: 25px 0 13px;
+ margin: 1em 0;
+}
+.imagify-options-subtitle + .imagify-setting-line {
+ border-top: 0;
+ padding-top: 8px;
+}
+
+/* 3 inlined buttons + Visual comparison */
+.imagify-setting-optim-level {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+}
+
+.imagify-setting-optim-level {
+ padding: 8px 0 18px;
+}
+.imagify-setting-optim-level > p {
+ margin: 0;
+}
+.imagify-setting-optim-level .imagify-inline-options {
+ flex-basis: 60%;
+ flex-grow: 1;
+ width: auto;
+ display: flex;
+ background: #2E3243;
+ border-radius: 3px;
+}
+.imagify-setting-optim-level .imagify-inline-options label {
+ display: block !important;
+ width: 100%;
+ font-size: 14px !important;
+ border-radius: 3px!important;
+}
+.imagify-setting-optim-level .imagify-visual-comparison-text {
+ flex-basis: 40%;
+ flex-shrink: 1;
+ padding-left: 20px;
+ color: #626E7B;
+ box-sizing: border-box;
+}
+.imagify-setting-optim-level.imagify-setting-optim-level .imagify-visual-comparison-btn {
+ padding-top: 5px;
+ margin-top: 2px;
+ border-radius: 2px;
+ text-transform: none;
+ letter-spacing: 0;
+ text-shadow: none!important;
+}
+
+/* TODO: maybe remove table lines, we don't use theme anymore⦠*/
+@media (max-width: 782px) {
+ .imagify-settings .form-table th {
+ padding-top: 2em;
+ padding-bottom: .5em;
+ }
+}
+.imagify-settings .form-table td {
+ vertical-align: top;
+}
+.imagify-settings .form-table th span {
+ cursor: pointer;
+}
+.imagify-middle th {
+ padding-top: 35px;
+}
+
+.imagify-settings div.submit.submit {
+ border-top: 1px solid #D9D9D9;
+ margin-top: 2em;
+ padding: 18px 0 7px 30px;
+}
+.imagify-settings .hidden + div.submit.submit {
+ margin-top: -1px;
+}
+.imagify-settings p.submit {
+ float: left;
+ margin-top: 0;
+}
+.imagify-settings p.submit .button {
+ margin: 0 5px;
+}
+
+.imagify-sub-header th {
+ text-align: right;
+}
+.imagify-sub-header .form-table {
+ margin: 0;
+}
+.imagify-sub-header th,
+.imagify-sub-header td {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.imagify-sub-header [for="api_key"] {
+ padding-top: 5px;
+}
+
+@media (max-width: 1120px) {
+ .imagify-settings .imagify-logo-block {
+ margin-right: 0;
+ }
+ .imagify-settings .imagify-rate-us.imagify-rate-us {
+ margin: 1em 0 -1em;
+ }
+}
+
+.imagify-settings .imagify-rate-us {
+ margin-right: 25px;
+ margin-left: auto;
+}
+
+/* Label & fake labels */
+label + .imagify-info,
+.imagify-visual-label {
+ display: inline-block;
+ width: 550px;
+ max-width: calc(100% - 38px);
+ margin-left: 38px;
+ padding-right: 25px;
+}
+.imagify-options-line {
+ -webkit-transition: opacity .3s;
+ transition: opacity .3s;
+}
+label ~ .imagify-options-line {
+ display: block;
+ margin: 8px 0 20px 40px;
+ font-size: 14px;
+}
+.imagify-options-line + .imagify-info {
+ margin-left: 38px;
+}
+label + .imagify-info {
+ margin-top: 10px;
+}
+.imagify-options-line + .imagify-info + .imagify-options-line {
+ margin-top: 20px;
+}
+.imagify-visual-label {
+ vertical-align: -5px;
+}
+label[for="imagify_sizes_full"] + .imagify-info {
+ vertical-align: middle;
+}
+
+.imagify-settings.imagify-settings [type="checkbox"]:not(:checked) + label ~ .imagify-options-line,
+.imagify-settings.imagify-settings [type="checkbox"]:not(:checked) + label .imagify-visual-label,
+:checked + label ~ .imagify-options-line :checked + label ~ .imagify-options-line .imagify-faded {
+ opacity: .5;
+}
+.imagify-settings.imagify-settings [type="checkbox"]:checked + label ~ .imagify-options-line,
+.imagify-settings.imagify-settings [type="checkbox"]:checked + label .imagify-visual-label,
+.imagify-settings.imagify-settings :not(:checked) + label ~ .imagify-options-line :not(:checked) + label ~ .imagify-options-line {
+ opacity: 1;
+}
+
+.imagify-radio-group + .imagify-options-line {
+ display: block;
+ margin: 0 0 0 1.7em;
+ font-size: 14px;
+}
+
+.imagify-checkbox-marged {
+ max-width: 500px;
+ margin-left: 45px;
+}
+
+.imagify-settings [type="text"],
+.imagify-settings [type="number"] {
+ width: 20em;
+ height: auto;
+ padding: 6px;
+ margin: 0 6px;
+ border: 1px solid #8BA6B4;
+ box-shadow: none;
+ border-radius: 2px;
+ color: #338EA6;
+ font-weight: bold;
+}
+.imagify-settings [type="number"] {
+ width: 5em;
+}
+.imagify-settings ::-webkit-input-placeholder {
+ color: #B1B1B1;
+ font-weight: 400;
+}
+.imagify-settings ::-moz-placeholder {
+ color: #B1B1B1;
+ font-weight: 400;
+ opacity: 1;
+}
+.imagify-settings :-ms-input-placeholder {
+ color: #B1B1B1;
+ font-weight: 400;
+}
+.imagify-settings :-moz-placeholder {
+ color: #B1B1B1;
+ font-weight: 400;
+ opacity: 1;
+}
+.imagify-settings ::placeholder {
+ color: #B1B1B1;
+ font-weight: 400;
+}
+
+.imagify-menu-bar-img {
+ box-sizing: border-box;
+ max-width: 100%;
+ height: auto;
+ margin-top: 0;
+ border: 1px solid #8BA6B4;
+}
+
+/* Layout */
+.imagify-col.imagify-main {
+ float: left;
+ width: calc(100% - 320px);
+ padding-left: 0;
+ padding-right: 0;
+}
+.imagify-have-rocket .imagify-main {
+ float: none;
+ width: 1265px;
+ max-width: 100%;
+}
+.imagify-sidebar {
+ float: left;
+ width: 300px;
+ max-width: 100%;
+}
+
+/* Sidebar with Ads */
+.imagify-sidebar-section {
+ border: 1px solid #BBB;
+ background: #1F2332;
+}
+.imagify-sidebar-section + .imagify-sidebar-section {
+ margin-top: 2em;
+}
+
+@media (max-width: 820px) {
+ .imagify-settings {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ }
+ .imagify-main {
+ width: auto;
+ float: none;
+ }
+ .imagify-sidebar {
+ order: 2;
+ float: none;
+ width: auto;
+ max-width: none;
+ margin-left: 0;
+ margin-top: 25px;
+ }
+ .wp-media-products {
+ text-align: center;
+ }
+ .wp-media-products li {
+ display: inline-block;
+ width: 100%;
+ max-width: 276px;
+ }
+}
+@media (min-width: 1400px) {
+ .imagify-main {
+ width: 74%;
+ }
+}
+.imagify-sidebar-section {
+ position: relative;
+ padding: 10px 20px;
+ text-align: center;
+ color: #F2F2F2;
+}
+.imagify-sidebar-close {
+ position: absolute;
+ top: 8px;
+ right: 12px;
+ text-decoration: none;
+}
+.imagify-sidebar-close i {
+ font-size: 2em;
+ color: rgba(255,255,255,.5);
+}
+p.imagify-sidebar-title {
+ margin: 1.2em 0 1.5em;
+ text-align: left;
+ color: #F56640;
+ text-transform: uppercase;
+ letter-spacing: 0.015em;
+ word-spacing: 0.015em;
+ font-weight: bold;
+}
+p.imagify-sidebar-description {
+ margin: 1.5em 0;
+ text-align: left;
+ font-weight: 500;
+ color: #F2F2F2;
+}
+.imagify-sidebar-description strong {
+ color: #39CE9A;
+}
+.imagify-rocket-cta-promo {
+ display: block;
+ padding: 8px 10px;
+ margin: 1.3em 0 .5em 0;
+ border: 2px dashed #F56640;
+ border-radius: 3px;
+ font-size: 18px;
+ font-weight: bold;
+ color: #F56640;
+}
+.imagify-rocket-cta-promo strong {
+ color: #F2F2F2;
+}
+a.btn-rocket {
+ display: block;
+ font-size: 15px;
+ padding: 10px 12px;
+ margin: 0 0 1.5em;
+ background: #F56640;
+ border-radius: 3px;
+ color: #F2F2F2;
+ text-transform: uppercase;
+ font-weight: bold;
+ text-decoration: none;
+}
+a.btn-rocket:hover,
+a.btn-rocket:focus {
+ background: #AC2B15;
+}
+
+.imagify-sidebar-section ul {
+ margin-top: 20px;
+}
+
+.imagify-sidebar-section li {
+ position: relative;
+ margin: 1.2em 0;
+ padding-left: 25px;
+ text-align: left;
+}
+.imagify-sidebar-section li:before {
+ content: "â";
+ position: absolute;
+ left: 0; top: 0;
+ color: #39CE9A;
+ font-size: 18px;
+}
+
+/* Menu in admin bar label exception */
+label[for="imagify_admin_bar_menu"],
+label[for="imagify_partner_links"] {
+ font-weight: normal !important;
+ color: #626E7B !important;
+}
+
+/* Select / Unselect all buttons */
+.imagify-select-all-buttons {
+ margin-top: 8px;
+}
+.imagify-link-like.imagify-select-all {
+ font-weight: bold;
+ font-size: 12px;
+ color: #3694AE;
+}
+.imagify-select-all.imagify-is-inactive {
+ color: inherit;
+ text-decoration: none;
+ cursor: default;
+}
+
+/* Add Themes box */
+.imagify-fts-header {
+ padding: 10px 16px;
+ background: #343A49;
+ color: #FFF;
+}
+.imagify-fts-header i {
+ font-size: 1.8em;
+ margin-right: 12px;
+}
+.imagify-fts-header p {
+ margin: 0;
+ color: #FFF;
+}
+.imagify-fts-header strong,
+#imagify-add-themes-to-custom-folder strong {
+ color: #40B1D0;
+ font-weight: bold;
+}
+
+.imagify-fts-content {
+ padding: 16px;
+ background: #F4F7F9;
+ border: 1px solid #CDD0D4;
+ border-top: 0;
+}
+
+.imagify-fts-content p {
+ margin-top: 0;
+}
+
+.imagify-kindof-title {
+ margin-top: 2em;
+ padding: 0 0 10px 0;
+ border-bottom: 1px solid #D2D3D6;
+ justify-content: space-between;
+ font-weight: bold;
+}
+.imagify-settings .imagify-button-mini {
+ padding: 4px 13px 4px 10px;
+}
+.imagify-settings .imagify-button-mini .dashicons-plus {
+ vertical-align: -7.5px;
+}
+.imagify-settings .imagify-button-mini.imagify-button-primary:hover,
+.imagify-settings .imagify-button-mini.imagify-button-primary:focus {
+ color: #FFF;
+}
+
+p.imagify-custom-folder-line {
+ position: relative;
+ margin: 0;
+ padding: 12px 15px;
+ color: #4A5362;
+ font-weight: 500;
+ transition: all .75s;
+}
+.imagify-custom-folder-line.imagify-will-remove {
+ background: #C51162;
+ color: #FFF;
+ transform: translateX(-120px);
+ opacity: 0;
+}
+.imagify-custom-folder-line:first-child {
+ margin-top: -.5em;
+}
+.imagify-custom-folder-line + .imagify-custom-folder-line {
+ border-top: 1px solid #E9EFF2;
+}
+
+.imagify-custom-folders-remove {
+ position: absolute;
+ right: 0;
+ top: 6px;
+ border: 0;
+ padding: 5px 10px 4px;
+ box-shadow: none;
+ color: #7A8996;
+ border-radius: 16px;
+ font-size: 13px;
+ line-height: 18px;
+ background: #FFF;
+ transition: all .275s;
+ cursor: pointer;
+}
+.imagify-custom-folders-remove-text {
+ max-width: 0;
+ overflow: hidden;
+ white-space: nowrap;
+ display: inline-block;
+ transform: scale(0);
+ opacity: 0;
+ transition: all .275s;
+}
+.imagify-custom-folders-remove:hover,
+.imagify-custom-folders-remove:focus {
+ background: #D9EFF6;
+ color: #225E6E;
+}
+.imagify-custom-folders-remove:hover .imagify-custom-folders-remove-text,
+.imagify-custom-folders-remove:focus .imagify-custom-folders-remove-text {
+ max-width: 6em;
+ transform: scale(1);
+ opacity: 1;
+}
+
+/* Progress bar */
+.imagify-settings .progress {
+ height: 8px;
+ margin-top: 1em;
+ background: #343A49;
+}
+.imagify-settings .bar {
+ position: relative;
+ width: 1px;
+ height: 8px;
+ background: #46B1CE;
+ -webkit-transition: width .5s;
+ transition: width .5s;
+}
+.imagify-settings .percent {
+ position: absolute;
+ top: 6px;
+ right: 0;
+ padding: 0 5px;
+ line-height: 1.85;
+ font-size: 14px;
+ font-weight: bold;
+ color: #40B1D0;
+}
+
+/* Icon rotation */
+.dashicons.rotate {
+ -webkit-animation: icon-rotate 2.6s infinite linear;
+ animation: icon-rotate 2.6s infinite linear;
+}
+
+@-webkit-keyframes icon-rotate {
+ from {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ to {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@keyframes icon-rotate {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* Files tree Part */
+.imagify-add-custom-folder + .imagify-loader {
+ display: none;
+ vertical-align: middle;
+}
+.imagify-add-custom-folder[disabled] + .imagify-loader {
+ display: inline-block;
+}
+
+.imagify-folders-information {
+ position: relative;
+ margin: -5px 0 20px 0;
+ padding: 10px 10px 10px 40px;
+ text-align: left;
+ background: #F2F2F2;
+}
+.imagify-folders-information i {
+ position: absolute;
+ left: 10px;
+ top: 50%;
+ margin-top: -10px;
+}
+
+/* Tree */
+.imagify-folders-tree {
+ margin: 0;
+ text-align: left;
+}
+.imagify-folders-tree li {
+ clear: left;
+}
+.imagify-folders-tree .imagify-folder {
+ box-sizing: border-box;
+ position: relative;
+ width: 48px;
+ z-index: 2;
+ float: left;
+ margin-top: -3px;
+ padding: 0 8px 0 0;
+ border: 0;
+ background: transparent!important;
+ box-shadow: none;
+ cursor: pointer;
+ transition: all .275s;
+}
+.imagify-folders-tree span.imagify-folder {
+ padding-left: 1.5px;
+}
+.imagify-folders-tree .imagify-folder:before {
+ content: "+";
+ display: inline-block;
+ width: 13px;
+ height: 21px;
+ font-size: 1.5em;
+ vertical-align: .15em;
+}
+.imagify-folders-tree span.imagify-folder:before {
+ content: '';
+}
+.imagify-folders-tree .imagify-folder-icon path {
+ transition: all .275s;
+}
+.imagify-folders-tree .imagify-is-open .imagify-folder-icon path {
+ stroke: #7A8996;
+}
+.imagify-folders-tree .imagify-is-open.imagify-folder:before {
+ content: "-";
+ color: #7A8996;
+}
+.imagify-folders-tree .imagify-is-open ~ label {
+ color: #7A8996;
+}
+.imagify-folders-tree .imagify-folder.imagify-loading:before,
+.imagify-folders-tree .imagify-folder .imagify-loader {
+ display: none;
+}
+.imagify-folders-tree .imagify-folder.imagify-loading .imagify-loader {
+ display: inline-block;
+ width: 13px;
+ height: 21px;
+ font-size: 1.5em;
+ vertical-align: .15em;
+}
+.imagify-folders-tree .imagify-folder.imagify-loading .imagify-loader img {
+ display: inline-block;
+ width: 100%;
+ height: auto;
+ vertical-align: middle;
+}
+
+.imagify-folders-tree button.imagify-folder:hover,
+.imagify-folders-tree button.imagify-folder:focus,
+.imagify-folders-tree button.imagify-folder:hover path,
+.imagify-folders-tree button.imagify-folder:focus path {
+ color: #3694AE;
+ stroke: #3694AE;
+}
+
+.imagify-folders-tree .imagify-folder:disabled,
+.imagify-folders-tree .imagify-folder.disabled {
+ color: rgb(127, 127, 127);
+}
+.imagify-swal-content .imagify-folders-tree label {
+ position: relative;
+ display: block;
+ width: 100%;
+ padding: 3px 0;
+ font-size: 15px;
+ font-weight: 500;
+ vertical-align: top;
+ transition: all .475s;
+}
+.imagify-swal-content .imagify-folders-tree label:hover,
+.imagify-folders-tree input:focus + label {
+ background: #F4F7F9;
+}
+
+.imagify-folders-tree .imagify-folder-already-selected label,
+.imagify-folders-tree .imagify-folder-already-selected label:hover,
+.imagify-folders-tree .imagify-folder-already-selected input:focus + label {
+ background: #40B1D0;
+ color: #FFF;
+ border-radius: 3px;
+ cursor: default;
+}
+.imagify-folders-tree .imagify-folder-already-selected button,
+.imagify-folders-tree .imagify-folder-already-selected button path {
+ color: #FFF;
+ stroke: #FFF;
+ cursor: default;
+}
+.imagify-folders-tree .imagify-folder-already-selected button:hover path,
+.imagify-folders-tree .imagify-folder-already-selected button:focus path {
+ stroke: #FFF;
+}
+.imagify-folders-tree .imagify-folder-already-selected button:before {
+ content: '';
+}
+
+/* Add Folder fake checkbox */
+.imagify-add-ed-folder {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.02em;
+ word-spacing: 0.02em;
+ color: #3694AE;
+ background: #F4F7F9;
+ opacity: 0;
+ transform: translateX(15px);
+ transition: all .275s;
+}
+label:hover .imagify-add-ed-folder,
+input:focus + label .imagify-add-ed-folder,
+input:checked + label .imagify-add-ed-folder,
+.imagify-folder-already-selected .imagify-add-ed-folder {
+ opacity: 1;
+ transform: translateX(0);
+}
+input:checked + label .imagify-add-ed-folder {
+ background: #FFF;
+}
+input:checked:focus + label .imagify-add-ed-folder,
+input:checked + label:hover .imagify-add-ed-folder {
+ background: #F4F7F9;
+}
+.imagify-folder-already-selected .imagify-add-ed-folder {
+ background: #40B1D0;
+ color: #FFF;
+}
+.imagify-fake-checkbox {
+ position: relative;
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ margin: 3.5px 15px 0 5px;
+ border: 1.5px solid #3694AE;
+ border-radius: 3px;
+ vertical-align: -4px;
+}
+.imagify-fake-checkbox:after {
+ position: absolute;
+ left: 1px;
+ top: 0;
+ content: "â";
+ color: #3694AE;
+ font-size: 14px;
+ line-height: .9;
+ font-style: normal;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+ opacity: 0;
+ transform: scale(0);
+ transition: all .475s;
+}
+input:checked + label .imagify-fake-checkbox:after,
+.imagify-folder-already-selected .imagify-fake-checkbox:after {
+ opacity: 1;
+ transform: scale(1);
+}
+.imagify-folder-already-selected .imagify-fake-checkbox {
+ border-color: #40B1D0;
+}
+.imagify-folder-already-selected .imagify-fake-checkbox:after {
+ color: #FFF;
+}
+
+/* Sub Trees */
+.imagify-folders-sub-tree {
+ position: relative;
+ margin-left: .75em;
+ padding-top: .6em;
+ padding-left: 1em;
+ border-left: 1px dotted rgba(98, 110, 123, .3);
+}
+.imagify-folders-sub-tree li {
+ position: relative;
+ margin-bottom: 4px;
+}
+.imagify-folders-sub-tree li:before {
+ content: "";
+ position: absolute;
+ top: 12px;
+ left: -1em;
+ height: 1px;
+ width: 0.9em;
+ border-top: 1px dotted rgba(98, 110, 123, .3);
+}
+.imagify-folders-sub-tree li:last-child:after {
+ content: "";
+ position: absolute;
+ left: -1.1em;
+ bottom: 0;
+ height: 11px;
+ width: 3px;
+ background: #FFF;
+}
+
+.imagify-empty-folder {
+ margin-top: -.5em;
+}
+.imagify-empty-folder em {
+ font-size: 12px;
+ font-weight: 500;
+ color: #A2AFBC;
+}
diff --git a/wp-content/plugins/imagify/assets/css/options.min.css b/wp-content/plugins/imagify/assets/css/options.min.css
new file mode 100644
index 00000000..beb49295
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/options.min.css
@@ -0,0 +1 @@
+.wrap.imagify-settings{margin-right:0}.imagify-settings.imagify-have-rocket{margin-right:20px}#imagify-check-api-container{display:block;margin-top:6px;font-weight:700}#imagify-check-api-container .dashicons{font-size:25px}#imagify-check-api-container .dashicons-no:before{color:#f06e57;vertical-align:-1px}#imagify-check-api-container .imagify-icon{font-size:1.8em;margin-right:3px;margin-left:1px;color:#8BC34A;vertical-align:-2px}.imagify-account-info-col .imagify-api-line{padding:22px 26px;background:#343A49}.imagify-api-line label,p.imagify-api-key-invite-title{display:block;margin-bottom:6px;font-size:14px;text-transform:uppercase;letter-spacing:.02em;font-weight:700;color:#343A49}.imagify-account-info-col .imagify-api-line label{color:#E5EBEF;display:inline-block}.imagify-api-line.imagify-api-line input[type=text]{width:100%;padding:6px 10px;border:1px solid #40B1D0;font-family:"PT Mono",Consolas,monospace;font-size:14px;letter-spacing:.01em;font-weight:700;color:#40B1D0;background:0 0;-webkit-box-shadow:none;box-shadow:none}.imagify-no-api-key .imagify-api-line{margin:3em 0 0;padding:2em 0 0;border-top:1px solid #D5D6D9}.imagify-no-api-key .imagify-api-line input[type=text]{margin-top:5px;width:400px;max-width:100%}.imagify-settings .imagify-no-api-key div.submit.submit{border:0;padding:0 16px;margin-top:0;background:#FFF}.imagify-settings .imagify-no-api-key div.submit.submit p{padding-bottom:0}.imagify-options-title{margin:.75em 0 0;font-size:24px;letter-spacing:.02em;font-weight:700;color:#343A49}.imagify-options-subtitle{padding-bottom:.3em;margin-bottom:20px;border-bottom:1px solid #D2D3D6;font-size:14px;letter-spacing:.01em;font-weight:700;text-transform:uppercase;color:#626E7B}.imagify-options-subtitle a{font-size:12px;color:#338EA6;text-transform:none;letter-spacing:0}.imagify-options-subtitle .imagify-info{margin-left:15px}.imagify-setting-line{border-top:1px solid #D2D3D6;padding:25px 0 13px;margin:1em 0}.imagify-options-subtitle+.imagify-setting-line{border-top:0;padding-top:8px}.imagify-setting-optim-level{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:8px 0 18px}.imagify-setting-optim-level>p{margin:0}.imagify-setting-optim-level .imagify-inline-options{-ms-flex-preferred-size:60%;flex-basis:60%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;width:auto;display:-webkit-box;display:-ms-flexbox;display:flex;background:#2E3243;border-radius:3px}.imagify-setting-optim-level .imagify-inline-options label{display:block!important;width:100%;font-size:14px!important;border-radius:3px!important}.imagify-setting-optim-level .imagify-visual-comparison-text{-ms-flex-preferred-size:40%;flex-basis:40%;-ms-flex-negative:1;flex-shrink:1;padding-left:20px;color:#626E7B;-webkit-box-sizing:border-box;box-sizing:border-box}.imagify-setting-optim-level.imagify-setting-optim-level .imagify-visual-comparison-btn{padding-top:5px;margin-top:2px;border-radius:2px;text-transform:none;letter-spacing:0;text-shadow:none!important}@media (max-width:782px){.imagify-settings .form-table th{padding-top:2em;padding-bottom:.5em}}.imagify-settings .form-table td{vertical-align:top}.imagify-settings .form-table th span{cursor:pointer}.imagify-middle th{padding-top:35px}.imagify-settings div.submit.submit{border-top:1px solid #D9D9D9;margin-top:2em;padding:18px 0 7px 30px}.imagify-settings .hidden+div.submit.submit{margin-top:-1px}.imagify-settings p.submit{float:left;margin-top:0}.imagify-settings p.submit .button{margin:0 5px}.imagify-sub-header th{text-align:right}.imagify-sub-header .form-table{margin:0}.imagify-sub-header td,.imagify-sub-header th{padding-top:0;padding-bottom:0}.imagify-sub-header [for=api_key]{padding-top:5px}@media (max-width:1120px){.imagify-settings .imagify-logo-block{margin-right:0}.imagify-settings .imagify-rate-us.imagify-rate-us{margin:1em 0 -1em}}.imagify-settings .imagify-rate-us{margin-right:25px;margin-left:auto}.imagify-visual-label,label+.imagify-info{display:inline-block;width:550px;max-width:calc(100% - 38px);margin-left:38px;padding-right:25px}.imagify-options-line{-webkit-transition:opacity .3s;-o-transition:opacity .3s;transition:opacity .3s}label~.imagify-options-line{display:block;margin:8px 0 20px 40px;font-size:14px}.imagify-options-line+.imagify-info{margin-left:38px}label+.imagify-info{margin-top:10px}.imagify-options-line+.imagify-info+.imagify-options-line{margin-top:20px}.imagify-visual-label{vertical-align:-5px}label[for=imagify_sizes_full]+.imagify-info{vertical-align:middle}.imagify-settings.imagify-settings [type=checkbox]:not(:checked)+label .imagify-visual-label,.imagify-settings.imagify-settings [type=checkbox]:not(:checked)+label~.imagify-options-line,:checked+label~.imagify-options-line :checked+label~.imagify-options-line .imagify-faded{opacity:.5}.imagify-settings.imagify-settings :not(:checked)+label~.imagify-options-line :not(:checked)+label~.imagify-options-line,.imagify-settings.imagify-settings [type=checkbox]:checked+label .imagify-visual-label,.imagify-settings.imagify-settings [type=checkbox]:checked+label~.imagify-options-line{opacity:1}.imagify-radio-group+.imagify-options-line{display:block;margin:0 0 0 1.7em;font-size:14px}.imagify-checkbox-marged{max-width:500px;margin-left:45px}.imagify-settings [type=text],.imagify-settings [type=number]{width:20em;height:auto;padding:6px;margin:0 6px;border:1px solid #8BA6B4;-webkit-box-shadow:none;box-shadow:none;border-radius:2px;color:#338EA6;font-weight:700}.imagify-settings [type=number]{width:5em}.imagify-settings ::-webkit-input-placeholder{color:#B1B1B1;font-weight:400}.imagify-settings ::-moz-placeholder{color:#B1B1B1;font-weight:400;opacity:1}.imagify-settings :-ms-input-placeholder{color:#B1B1B1;font-weight:400}.imagify-settings :-moz-placeholder{color:#B1B1B1;font-weight:400;opacity:1}.imagify-settings ::-ms-input-placeholder{color:#B1B1B1;font-weight:400}.imagify-settings ::placeholder{color:#B1B1B1;font-weight:400}.imagify-menu-bar-img{-webkit-box-sizing:border-box;box-sizing:border-box;max-width:100%;height:auto;margin-top:0;border:1px solid #8BA6B4}.imagify-col.imagify-main{float:left;width:calc(100% - 320px);padding-left:0;padding-right:0}.imagify-have-rocket .imagify-main{float:none;width:1265px;max-width:100%}.imagify-sidebar{float:left;width:300px;max-width:100%}.imagify-sidebar-section{border:1px solid #BBB;background:#1F2332;position:relative;padding:10px 20px;text-align:center;color:#F2F2F2}.imagify-sidebar-section+.imagify-sidebar-section{margin-top:2em}@media (max-width:820px){.imagify-main,.imagify-sidebar{float:none;width:auto}.imagify-settings{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.imagify-sidebar{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2;max-width:none;margin-left:0;margin-top:25px}.wp-media-products{text-align:center}.wp-media-products li{display:inline-block;width:100%;max-width:276px}}@media (min-width:1400px){.imagify-main{width:74%}}.imagify-sidebar-close{position:absolute;top:8px;right:12px;text-decoration:none}.imagify-sidebar-close i{font-size:2em;color:rgba(255,255,255,.5)}p.imagify-sidebar-title{margin:1.2em 0 1.5em;text-align:left;color:#F56640;text-transform:uppercase;letter-spacing:.015em;word-spacing:.015em;font-weight:700}p.imagify-sidebar-description{margin:1.5em 0;text-align:left;font-weight:500;color:#F2F2F2}.imagify-sidebar-description strong{color:#39CE9A}.imagify-rocket-cta-promo{display:block;padding:8px 10px;margin:1.3em 0 .5em;border:2px dashed #F56640;border-radius:3px;font-size:18px;font-weight:700;color:#F56640}.imagify-rocket-cta-promo strong,a.btn-rocket{color:#F2F2F2}a.btn-rocket{display:block;font-size:15px;padding:10px 12px;margin:0 0 1.5em;background:#F56640;border-radius:3px;text-transform:uppercase;font-weight:700;text-decoration:none}a.btn-rocket:focus,a.btn-rocket:hover{background:#AC2B15}.imagify-sidebar-section ul{margin-top:20px}.imagify-sidebar-section li{position:relative;margin:1.2em 0;padding-left:25px;text-align:left}.imagify-sidebar-section li:before{content:"â";position:absolute;left:0;top:0;color:#39CE9A;font-size:18px}label[for=imagify_admin_bar_menu],label[for=imagify_partner_links]{font-weight:400!important;color:#626E7B!important}.imagify-select-all-buttons{margin-top:8px}.imagify-link-like.imagify-select-all{font-weight:700;font-size:12px;color:#3694AE}.imagify-select-all.imagify-is-inactive{color:inherit;text-decoration:none;cursor:default}.imagify-fts-header{padding:10px 16px;background:#343A49;color:#FFF}.imagify-fts-header i{font-size:1.8em;margin-right:12px}.imagify-fts-header p{margin:0;color:#FFF}#imagify-add-themes-to-custom-folder strong,.imagify-fts-header strong{color:#40B1D0;font-weight:700}.imagify-fts-content{padding:16px;background:#F4F7F9;border:1px solid #CDD0D4;border-top:0}.imagify-fts-content p{margin-top:0}.imagify-kindof-title{margin-top:2em;padding:0 0 10px;border-bottom:1px solid #D2D3D6;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-weight:700}.imagify-settings .imagify-button-mini{padding:4px 13px 4px 10px}.imagify-settings .imagify-button-mini .dashicons-plus{vertical-align:-7.5px}.imagify-settings .imagify-button-mini.imagify-button-primary:focus,.imagify-settings .imagify-button-mini.imagify-button-primary:hover{color:#FFF}p.imagify-custom-folder-line{position:relative;margin:0;padding:12px 15px;color:#4A5362;font-weight:500;-webkit-transition:all .75s;-o-transition:all .75s;transition:all .75s}.imagify-custom-folder-line:first-child,.imagify-empty-folder{margin-top:-.5em}.imagify-custom-folder-line.imagify-will-remove{background:#C51162;color:#FFF;-webkit-transform:translateX(-120px);-ms-transform:translateX(-120px);transform:translateX(-120px);opacity:0}.imagify-custom-folder-line+.imagify-custom-folder-line{border-top:1px solid #E9EFF2}.imagify-custom-folders-remove{position:absolute;right:0;top:6px;border:0;padding:5px 10px 4px;-webkit-box-shadow:none;box-shadow:none;color:#7A8996;border-radius:16px;font-size:13px;line-height:18px;background:#FFF;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s;cursor:pointer}.imagify-custom-folders-remove-text{max-width:0;overflow:hidden;white-space:nowrap;display:inline-block;-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);opacity:0;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-custom-folders-remove:focus,.imagify-custom-folders-remove:hover{background:#D9EFF6;color:#225E6E}.imagify-custom-folders-remove:focus .imagify-custom-folders-remove-text,.imagify-custom-folders-remove:hover .imagify-custom-folders-remove-text{max-width:6em;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1);opacity:1}.imagify-settings .progress{height:8px;margin-top:1em;background:#343A49}.imagify-settings .bar{position:relative;width:1px;height:8px;background:#46B1CE;-webkit-transition:width .5s;-o-transition:width .5s;transition:width .5s}.imagify-settings .percent{position:absolute;top:6px;right:0;padding:0 5px;line-height:1.85;font-size:14px;font-weight:700;color:#40B1D0}.dashicons.rotate{-webkit-animation:icon-rotate 2.6s infinite linear;animation:icon-rotate 2.6s infinite linear}@-webkit-keyframes icon-rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes icon-rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.imagify-add-custom-folder+.imagify-loader{display:none;vertical-align:middle}.imagify-add-custom-folder[disabled]+.imagify-loader{display:inline-block}.imagify-folders-information{position:relative;margin:-5px 0 20px;padding:10px 10px 10px 40px;text-align:left;background:#F2F2F2}.imagify-folders-information i{position:absolute;left:10px;top:50%;margin-top:-10px}.imagify-folders-tree{margin:0;text-align:left}.imagify-folders-tree li{clear:left}.imagify-folders-tree .imagify-folder{-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;width:48px;z-index:2;float:left;margin-top:-3px;padding:0 8px 0 0;border:0;background:0 0!important;-webkit-box-shadow:none;box-shadow:none;cursor:pointer;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-folders-tree span.imagify-folder{padding-left:1.5px}.imagify-folders-tree .imagify-folder:before{content:"+";display:inline-block;width:13px;height:21px;font-size:1.5em;vertical-align:.15em}.imagify-folders-tree span.imagify-folder:before{content:''}.imagify-folders-tree .imagify-folder-icon path{-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-folders-tree .imagify-is-open .imagify-folder-icon path{stroke:#7A8996}.imagify-folders-tree .imagify-is-open.imagify-folder:before{content:"-";color:#7A8996}.imagify-folders-tree .imagify-is-open~label{color:#7A8996}.imagify-folders-tree .imagify-folder .imagify-loader,.imagify-folders-tree .imagify-folder.imagify-loading:before{display:none}.imagify-folders-tree .imagify-folder.imagify-loading .imagify-loader{display:inline-block;width:13px;height:21px;font-size:1.5em;vertical-align:.15em}.imagify-folders-tree .imagify-folder.imagify-loading .imagify-loader img{display:inline-block;width:100%;height:auto;vertical-align:middle}.imagify-folders-tree button.imagify-folder:focus,.imagify-folders-tree button.imagify-folder:focus path,.imagify-folders-tree button.imagify-folder:hover,.imagify-folders-tree button.imagify-folder:hover path{color:#3694AE;stroke:#3694AE}.imagify-folders-tree .imagify-folder.disabled,.imagify-folders-tree .imagify-folder:disabled{color:#7f7f7f}.imagify-swal-content .imagify-folders-tree label{position:relative;display:block;width:100%;padding:3px 0;font-size:15px;font-weight:500;vertical-align:top;-webkit-transition:all .475s;-o-transition:all .475s;transition:all .475s}.imagify-folders-tree input:focus+label,.imagify-swal-content .imagify-folders-tree label:hover{background:#F4F7F9}.imagify-folders-tree .imagify-folder-already-selected input:focus+label,.imagify-folders-tree .imagify-folder-already-selected label,.imagify-folders-tree .imagify-folder-already-selected label:hover{background:#40B1D0;color:#FFF;border-radius:3px;cursor:default}.imagify-folders-tree .imagify-folder-already-selected button,.imagify-folders-tree .imagify-folder-already-selected button path{color:#FFF;stroke:#FFF;cursor:default}.imagify-folders-tree .imagify-folder-already-selected button:focus path,.imagify-folders-tree .imagify-folder-already-selected button:hover path{stroke:#FFF}.imagify-folders-tree .imagify-folder-already-selected button:before{content:''}.imagify-add-ed-folder{position:absolute;top:0;bottom:0;right:0;font-size:11px;text-transform:uppercase;letter-spacing:.02em;word-spacing:.02em;color:#3694AE;background:#F4F7F9;opacity:0;-webkit-transform:translateX(15px);-ms-transform:translateX(15px);transform:translateX(15px);-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-folder-already-selected .imagify-add-ed-folder,input:checked+label .imagify-add-ed-folder,input:focus+label .imagify-add-ed-folder,label:hover .imagify-add-ed-folder{opacity:1;-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}input:checked+label .imagify-add-ed-folder{background:#FFF}input:checked+label:hover .imagify-add-ed-folder,input:checked:focus+label .imagify-add-ed-folder{background:#F4F7F9}.imagify-folder-already-selected .imagify-add-ed-folder{background:#40B1D0;color:#FFF}.imagify-fake-checkbox{position:relative;display:inline-block;width:14px;height:14px;margin:3.5px 15px 0 5px;border:1.5px solid #3694AE;border-radius:3px;vertical-align:-4px}.imagify-fake-checkbox:after{position:absolute;left:1px;top:0;content:"â";color:#3694AE;font-size:14px;line-height:.9;font-style:normal;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;opacity:0;-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);-webkit-transition:all .475s;-o-transition:all .475s;transition:all .475s}.imagify-folder-already-selected .imagify-fake-checkbox:after,input:checked+label .imagify-fake-checkbox:after{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.imagify-folder-already-selected .imagify-fake-checkbox{border-color:#40B1D0}.imagify-folder-already-selected .imagify-fake-checkbox:after{color:#FFF}.imagify-folders-sub-tree{position:relative;margin-left:.75em;padding-top:.6em;padding-left:1em;border-left:1px dotted rgba(98,110,123,.3)}.imagify-folders-sub-tree li{position:relative;margin-bottom:4px}.imagify-folders-sub-tree li:before{content:"";position:absolute;top:12px;left:-1em;height:1px;width:.9em;border-top:1px dotted rgba(98,110,123,.3)}.imagify-folders-sub-tree li:last-child:after{content:"";position:absolute;left:-1.1em;bottom:0;height:11px;width:3px;background:#FFF}.imagify-empty-folder em{font-size:12px;font-weight:500;color:#A2AFBC}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/pricing-modal.css b/wp-content/plugins/imagify/assets/css/pricing-modal.css
new file mode 100644
index 00000000..9db8668d
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/pricing-modal.css
@@ -0,0 +1,1154 @@
+/* Flexbox re-groups */
+.imagify-modal-cols,
+.imagify-border-styled,
+.imagify-offer-header,
+.imagify-payment-modal .imagify-modal-content,
+.imagify-flex-table,
+.imagify-tabs {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+}
+.imagify-modal-cols,
+.imagify-border-styled {
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+}
+
+.imagify-payment-modal {
+ text-align: center;
+ color: #7A8996;
+}
+.imagify-payment-modal * {
+ box-sizing: border-box;
+}
+.imagify-modal-loader {
+ position: absolute;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: #fff url('../images/loader-balls.svg') center no-repeat;
+ z-index: 10;
+}
+.imagify-payment-modal .imagify-modal-content {
+ width: 980px;
+ max-width: 100%;
+ min-width: 925px;
+ padding: 0;
+}
+.imagify-modal-content.imagify-iframe-viewing {
+ width: 980px;
+ height: 672px;
+ overflow: hidden;
+}
+.imagify-iframe-viewing #imagify-payment-process-view {
+ width: 980px;
+ height: 668px;
+}
+.imagify-payment-modal .imagify-modal-main {
+ width: 70%;
+}
+.imagify-iframe-viewing .imagify-modal-main {
+ width: auto;
+}
+.imagify-payment-modal .imagify-modal-content.imagify-success-viewing {
+ min-width: auto;
+ width: 450px;
+ min-height: 300px;
+}
+.imagify-success-viewing .imagify-modal-main {
+ width: 100%;
+}
+.imagify-payment-modal .imagify-modal-sidebar {
+ width: 30%;
+ padding: 15px 20px;
+ background: #1F2332;
+ color: #FFF;
+}
+.imagify-modal-content.imagify-iframe-viewing .imagify-modal-sidebar,
+.imagify-modal-content.imagify-success-viewing .imagify-modal-sidebar {
+ display: none;
+}
+.imagify-modal-section {
+ padding: 0 25px;
+}
+.imagify-modal-section.section-gray {
+ margin: 0 0 1em;
+ padding: 10px 25px 15px;
+ background: #F6F7FB;
+}
+.imagify-tabs-contents .section-gray {
+ padding: 8px 25px 10px;
+}
+.imagify-modal-section .imagify-modal-title:first-child {
+ margin-top: 1em;
+ margin-bottom: 1.5em;
+}
+.imagify-modal-section.section-gray .imagify-modal-title {
+ margin-top: .5em;
+ margin-bottom: .5em;
+}
+.imagify-modal-title {
+ font-size: 1.8em;
+}
+.imagify-modal-title .imagify-inner-sub-title {
+ display: block;
+ font-size: .56em;
+}
+.imagify-analyzing .imagify-numbers-calc,
+.imagify-numbers-notcalc,
+.imagify-modal-section.imagify-analyzing .imagify-modal-cols,
+.imagify-modal-section .imagify-loader {
+ display: none;
+}
+.imagify-analyzing .imagify-numbers-notcalc,
+.imagify-modal-section.imagify-analyzing .imagify-loader {
+ display: block;
+}
+.imagify-modal-section .imagify-loader {
+ margin: 2em auto;
+}
+
+.imagify-border-styled {
+ width: 200px;
+ margin: 0 auto;
+ color: #8BC34A;
+ font-weight: bold;
+ font-size: 0.925em;
+}
+.imagify-border-styled:before,
+.imagify-border-styled:after {
+ content: "";
+ height: 1px;
+ background: rgba(0,0,0,.1);
+ -webkit-flex-basis: 40px;
+ -ms-flex-preferred-size: 40px;
+ flex-basis: 40px;
+}
+.imagify-border-styled:before {
+ margin-right: 5px;
+}
+.imagify-border-styled:after {
+ margin-left: 5px;
+}
+.imagify-big-number {
+ font-size: 3.7em;
+ font-weight: bold;
+ margin: -3px 0;
+ color: #4A4A4A;
+ line-height: 1;
+}
+.imagify-payment-modal strong {
+ font-weight: bold;
+ color: #4A4A4A;
+}
+
+.imagify-popin-message {
+ padding: 5px 15px;
+ text-align: left;
+}
+.imagify-popin-message.imagify-error p {
+ color: #FFF;
+}
+
+.imagify-small-options {
+ width: 300px;
+ margin: 1em auto .5em;
+ background: #338EA6;
+ border-radius: 4px;
+}
+
+.imagify-small-options input[type="radio"]:not(:checked) + label,
+.imagify-small-options input[type="radio"]:checked + label {
+ padding: 8px 10px;
+ font-size: 13px;
+ color: #FFF;
+ box-shadow: none;
+ border-left: 0;
+}
+
+.imagify-small-options input[type="radio"]:not(:checked) + label {
+ background: #338EA6;
+ color: rgba(255, 255, 255, .4);
+}
+.imagify-small-options input[type="radio"]:checked + label {
+ background: #40B1D0;
+}
+
+.imagify-cols:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+
+.js .imagify-iframe-viewing .close-btn {
+ display: none;
+}
+
+.imagify-modal .imagify-cols {
+ padding: 0 20px;
+}
+.imagify-payment-modal .imagify-iconed {
+ margin: 1.5em 5em 1.5em 0;
+}
+
+
+.imagify-iconed {
+ position: relative;
+ text-align: left;
+ padding-left: 42px;
+ margin-right: 15px;
+ font-weight: 500;
+}
+.imagify-iconed .dashicons,
+.imagify-iconed .icon {
+ position: absolute;
+ font-size: 2em;
+ left: 0; top: 2px;
+ color: #40B1D0;
+}
+.imagify-payment-modal .close-btn {
+ top: 10px;
+ right: 10px;
+ width: 24px;
+ height: 24px;
+ padding: 2px 0 0 4.5px; /* Safari iOS bug fix */
+ color: #FFF;
+ background: #40B1D0;
+ border-radius: 50%;
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+.imagify-payment-modal .close-btn i {
+ margin-left: -3.5px;
+ margin-top: -0.5px;
+}
+.imagify-payment-modal .close-btn:hover {
+ background: #F6F7FB;
+}
+
+/* OFFERS */
+.imagify-offer-line {
+ margin-top: 1.5em;
+}
+.imagify-offer-line + .imagify-offer-line {
+ margin-top: 0.75em;
+}
+.imagify-offer-header {
+ -webkit-box-pack: justify;
+ -webkit-justify-content: space-between;
+ -ms-flex-pack: justify;
+ justify-content: space-between;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+ padding: 0 0 0 15px;
+ border-radius: 4px 4px 0 0;
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+.imagify-offer-header.imagify-offer-header.imagify-offer-header .imagify-inline-options label:last-child {
+ border-radius: 0 4px 0 0;
+}
+.imagify-offer-header .imagify-inline-options {
+ width: auto;
+}
+.imagify-offer-title {
+ font-weight: bold;
+ margin: 0;
+}
+.imagify-offer-header,
+.imagify-offer-header .imagify-inline-options input[type="radio"]:not(:checked) + label {
+ background: #E5EBEF;
+}
+.imagify-offer-onetime .imagify-offer-header {
+ padding-top:8px;
+ padding-bottom: 8px;
+}
+.imagify-offer-onetimes > div {
+ padding-top: 15px;
+ padding-bottom: 15px;
+}
+.imagify-offer-header .imagify-inline-options input[type="radio"]:not(:checked) + label,
+.imagify-offer-header .imagify-inline-options input[type="radio"]:checked + label {
+ position: relative;
+ padding: 7px 30px;
+ font-size: 1em;
+ letter-spacing: 0.05em;
+ color: inherit;
+ box-shadow: 0 0 0;
+ border-radius: 0;
+}
+.imagify-offer-header .imagify-inline-options input[type="radio"]:checked + label {
+ background: #F6F7FB;
+}
+
+.imagify-2-free {
+ position: absolute;
+ bottom: 100%; left: 0; right: 0;
+ padding: 2px 10px;
+ margin-bottom: 8px;
+ font-size: 0.8em;
+ letter-spacing: 0;
+ text-transform: none;
+ text-align: center;
+ color: #FFF;
+ background: #10121A;
+ border-radius: 2px;
+}
+.imagify-2-free:after {
+ content: "";
+ position: absolute;
+ left: 50%; bottom: -3px;
+ margin-left: -3px;
+ border-top: 3px solid #10121A;
+ border-left: 3px solid transparent;
+ border-right: 3px solid transparent;
+}
+/* right position */
+.imagify-2-free.imagify-b-right {
+ bottom: auto;
+ left: 100%; right: -100%;
+ margin-bottom: 0;
+ margin-left: 8px;
+}
+.imagify-2-free.imagify-b-right:after {
+ left: -3px; bottom: auto; top: 50%;
+ margin-top: -3px; margin-left: 0;
+ border-right: 3px solid #10121A;
+ border-top: 3px solid transparent;
+ border-bottom: 3px solid transparent;
+ border-left: 0;
+}
+
+/* bottom position */
+.imagify-2-free.imagify-b-bottom {
+ bottom: -100%;
+ left: 0; right: 0;
+ margin-top: 8px;
+}
+
+.imagify-2-free.imagify-b-bottom:after {
+ top: -3px; bottom: auto;
+ border-bottom: 3px solid #10121A;
+ border-left: 3px solid transparent;
+ border-right: 3px solid transparent;
+ border-top: 0;
+}
+
+.imagify-offer-content {
+ text-align: left;
+ background: #F6F7FB;
+ border-radius: 0 0 4px 4px;
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+.imagify-offer-onetime .imagify-offer-content {
+ padding: 10px 0;
+}
+
+/* Checkboxes adjustment */
+div.imagify-col-checkbox {
+ position: relative;
+ width: 25.5%;
+ padding-top: 10px;
+ padding-bottom: 7px;
+}
+.imagify-col-checkbox label {
+ display: block;
+}
+.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:not(:checked),
+.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:checked {
+ position: absolute;
+ top: 50%; left: 6px;
+ margin: -8px 0 0 0;
+}
+.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:not(:checked) + label:before,
+.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:checked + label:before {
+ margin: 0;
+ top: -2px;
+ left: 6px;
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:not(:checked) + label:after,
+.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:checked + label:after {
+ top: 1px;
+ left: 13px;
+}
+.imagify-col-checkbox label {
+ padding-left: 55px!important;
+}
+
+/* Offer col */
+.imagify-offer-size {
+ font-size: 30px;
+ color: #2E3243;
+ font-weight: bold;
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+.imagify-offer-by {
+ font-size: 10px;
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+.imagify-approx {
+ display: none;
+ font-size: 11px;
+ line-height: 1.2;
+ -webkit-transition: all .275s;
+ transition: all .275s;
+}
+
+div.imagify-col-price {
+ width: 35%;
+}
+.imagify-flex-table .imagify-price-block {
+ padding-left: 0;
+ padding-right: 0;
+}
+.imagify-offer-monthly .imagify-flex-table .imagify-price-block,
+.imagify-offer-monthlies .imagify-price-block {
+ padding-top: 0;
+}
+.imagify-flex-table .imagify-price-complement {
+ padding-right: 0;
+ font-size: 10px;
+ font-weight: bold;
+}
+.imagify-price-block,
+.imagify-price-discount {
+ white-space: nowrap;
+}
+.imagify-price-block span,
+.imagify-price-discount span {
+ display: inline-block;
+ vertical-align: middle;
+}
+.imagify-price-discount.imagify-price-discount {
+ position: relative;
+ flex-grow: 0;
+ padding-top: 15px;
+ font-weight: bold;
+ width: 70px;
+}
+.imagify-price-discount:before {
+ content: "";
+ position: absolute;
+ top: 25px;
+ width: 62%;
+ height: 2px;
+ background: #2E3243;
+ transform: rotate(-15deg);
+}
+.imagify-offer-onetimes .imagify-price-discount:before {
+ width: 100%;
+}
+.imagify-price-discount-dollar {
+ color: #2E3243;
+}
+.imagify-price-discount-number {
+ color: #8BA6B4;
+}
+.imagify-offer-selected .imagify-price-discount-number {
+ color: #FFF;
+}
+span.imagify-dollars {
+ color: #1F2332;
+ font-size: 18px;
+ font-weight: bold;
+ vertical-align: -2px;
+}
+.imagify-offer-onetime .imagify-col-price {
+ padding-top: 0;
+}
+.imagify-offer-onetime .imagify-dollars {
+ vertical-align: -1px;
+}
+.imagify-price-big,
+.imagify-price-mini {
+ color: #40B1D0;
+ font-weight: bold;
+}
+.imagify-price-big {
+ font-size: 36px;
+}
+span.imagify-price-mini {
+ font-size: 18px;
+ vertical-align: 2px;
+}
+span.imagify-price-by {
+ font-size: 10px;
+ color: #1F2332;
+ vertical-align: -13px;
+ text-indent: -27px;
+}
+
+.imagify-col-other-actions {
+ width: 18.5%;
+ text-align: right;
+}
+.imagify-col-other-actions a {
+ font-size: 11px;
+}
+
+/* Offer selected */
+.imagify-offer-selected,
+.imagify-offer-selected .imagify-offer-title,
+.imagify-offer-selected .imagify-offer-size,
+.imagify-offer-selected .imagify-price-big,
+.imagify-offer-selected .imagify-price-mini,
+.imagify-offer-selected .imagify-price-complement,
+.imagify-offer-selected .imagify-col-other-actions a {
+ color: #FFF;
+}
+.imagify-offer-selected .imagify-offer-header,
+.imagify-offer-selected .imagify-offer-header .imagify-inline-options input[type="radio"]:not(:checked) + label {
+ background: #338EA6;
+}
+.imagify-offer-selected .imagify-offer-header .imagify-inline-options input[type="radio"]:checked + label {
+ background: #40B1D0;
+}
+.imagify-offer-selected .imagify-offer-content {
+ background: #40B1D0;
+}
+.imagify-offer-selected .imagify-checkbox.imagify-checkbox:not(:checked) + label:before,
+.imagify-offer-selected .imagify-checkbox.imagify-checkbox:checked + label:before {
+ border-color: #FFF;
+ background: #40B1D0;
+}
+.imagify-offer-selected .imagify-checkbox.imagify-checkbox:not(:checked) + label:after,
+.imagify-offer-selected .imagify-checkbox.imagify-checkbox:checked + label:after {
+ color: #FFF;
+}
+.imagify-offer-selected .imagify-offer-by {
+ color: #2E3243;
+}
+
+.imagify-enough-title {
+ display: none;
+}
+.imagify-enough-free .imagify-not-enough-title {
+ display: none;
+}
+.imagify-enough-free .imagify-enough-title {
+ display: block;
+}
+
+.imagify-submit-line {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-pack: justify;
+ -ms-flex-pack: justify;
+ justify-content: space-between;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ margin: 2em 0;
+ text-align: left;
+}
+.imagify-coupon-section {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+}
+.imagify-coupon-section p {
+ margin: 0;
+ line-height: 1.3;
+}
+.imagify-coupon-text {
+ width: 200px;
+ max-width: 100%;
+ padding-right: 15px;
+}
+.imagify-coupon-loader {
+ display: none;
+}
+.imagify-coupon-text.checking {
+ text-align: right;
+}
+.imagify-coupon-text.checking .imagify-coupon-loader {
+ display: inline;
+}
+.imagify-coupon-text.checking label {
+ display: none;
+}
+.imagify-coupon-input {
+ position: relative;
+}
+.imagify-coupon-input input {
+ position: relative;
+ z-index: 1;
+}
+[id="imagify-coupon-validate"].button-secondary {
+ position: absolute;
+ top: 1px;
+ right: 3px;
+ bottom: 2px;
+ box-shadow: none;
+ padding: 4px 10px;
+ z-index: 0;
+ transition: transform .275s;
+}
+.imagify-canbe-validate [id="imagify-coupon-validate"] {
+ transform: translateX(45px);
+}
+
+/* Promotion/Discount section */
+.imagify-modal-section + .imagify-modal-promotion {
+ margin-top: -1em;
+}
+.imagify-modal-promotion {
+ position: relative;
+ overflow: hidden;
+ display: none;
+ align-items: center;
+ padding: 15px 25px;
+ background: #604D90;
+ text-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
+}
+.imagify-modal-promotion.active {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+}
+[id="imagify-pricing-tab-onetime"] .imagify-modal-promotion {
+ margin-bottom: 4em;
+}
+.imagify-modal-promotion:before {
+ content: "\f488";
+ position: absolute;
+ top: 28px;
+ left: 8%;
+ font-family: "dashicons";
+ font-size: 90px;
+ color: #8476A9;
+ text-shadow: none;
+}
+.imagify-modal-promotion p {
+ position: relative;
+ margin: .2em 0;
+ color: #FFF;
+}
+.imagify-promo-title {
+ -ms-flex-preferred-size: 100%;
+ flex-basis: 100%;
+ text-transform: uppercase;
+ font-size: 20px;
+ font-weight: bold;
+ letter-spacing: 0.125em;
+}
+.imagify-until-date {
+ -ms-flex-preferred-size: 200px;
+ flex-basis: 200px;
+ text-align: right;
+}
+.imagify-until-date strong {
+ color: #FFF;
+}
+
+
+.imagify-submit-line button {
+ font-size: 16px;
+}
+input.imagify-coupon-code {
+ padding: 10px;
+ border: 2px solid #7A8996;
+ font-size: 0.875em;
+ font-weight: bold;
+ border-radius: 3px;
+}
+.validated.imagify-coupon-section .imagify-coupon-text,
+.validated.imagify-coupon-section strong {
+ color: #8BC34A;
+}
+.validated.imagify-coupon-section .imagify-coupon-code {
+ color: #8BC34A;
+ border-color: #8BC34A;
+}
+.invalid.imagify-coupon-section .imagify-coupon-text,
+.invalid.imagify-coupon-section strong {
+ color: #d0021b;
+}
+.invalid.imagify-coupon-section .imagify-coupon-code {
+ color: #d0021b;
+ border-color: #d0021b;
+}
+.imagify-footer-lines {
+ width: 500px;
+ max-width: 100%;
+ margin: 2em auto 2.5em;
+ font-size: 0.85em;
+ line-height: 1.5;
+}
+
+/* Year selected */
+.imagify-year-selected .imagify-switch-my .imagify-yearly {
+ display: block;
+}
+.imagify-year-selected .imagify-switch-my .imagify-monthly {
+ display: none;
+}
+/* Month selected */
+.imagify-month-selected .imagify-switch-my .imagify-yearly {
+ display: none;
+}
+.imagify-month-selected .imagify-switch-my .imagify-monthly {
+ display: block;
+}
+
+/* Flexbox table */
+.imagify-flex-table {
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+}
+.imagify-flex-table > * {
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ padding: 7px 15px;
+}
+
+/* Pricing table */
+div.imagify-col-details {
+ width: 22%;
+ padding-left: 25px;
+}
+.imagify-col-details p {
+ margin: 0;
+}
+.imagify-pricing-table {
+ margin: 0 20px;
+}
+.imagify-pricing-table .imagify-offer-line {
+ padding: .6em 0;
+ border: 2px solid #E8EEF0;
+ text-align: left;
+ border-radius: 3px;
+}
+.imagify-pricing-table .imagify-offer-line:first-child {
+ margin-top: .75em;
+}
+.imagify-pricing-table .imagify-offer-line.imagify-offer-selected:first-child {
+ margin-top: 1.75em;
+}
+.imagify-pricing-table .imagify-offer-line + .imagify-offer-line {
+ margin-top: -2px;
+}
+.imagify-pricing-table .imagify-col-other-actions {
+ width: 20.5%;
+}
+.imagify-pricing-table .imagify-approx {
+ margin-left: 0;
+ line-height: 0.5;
+ margin-bottom: 1em;
+}
+.imagify-pricing-table .imagify-offer-selected {
+ -webkit-transform: scale(1.03);
+ transform: scale(1.03);
+ background: #40B1D0;
+ border-width: 0;
+}
+.imagify-pricing-table .imagify-offer-selected .imagify-approx {
+ color: #FFF;
+}
+.imagify-pricing-table .imagify-button-secondary {
+ padding: 3px 20px;
+ box-shadow: none;
+ text-transform: uppercase;
+ font-size: 12px;
+ letter-spacing: 0.025em;
+}
+.imagify-offer-selected.imagify-offer-selected .imagify-button-secondary {
+ border: 2px solid #FFF;
+ background: #40B1D0;
+ box-shadow: none;
+ text-shadow: none!important;
+}
+.imagify-offer-selected.imagify-offer-selected .imagify-button-secondary:hover,
+.imagify-offer-selected.imagify-offer-selected .imagify-button-secondary:focus {
+ background: #FFF;
+ color: #40B1D0;
+}
+
+.imagify-col .imagify-special-needs {
+ margin-left: 25px;
+}
+.imagify-special-needs strong {
+ font-size: 25px;
+ font-weight: bold;
+ color: #40B1D0;
+}
+.imagify-special-needs span {
+ display: block;
+ font-size: 12px;
+ margin-top: -.5em;
+}
+div.imagify-col-price {
+ position: relative;
+}
+
+/* we recommend line */
+.imagify-recommend {
+ display: none;
+ position: absolute;
+ left: -20px; bottom: 100%;
+ padding: 0;
+ margin-bottom: 8px;
+ color: #1F2332;
+ font-weight: bold;
+ font-style: italic;
+}
+.imagify-offer-selected .imagify-recommend {
+ display: block;
+}
+[class*="imagify-onetime-"] .imagify-recommend {
+ left: 65px;
+ margin-bottom: 20px;
+}
+.imagify-recommend:before {
+ content: "";
+ position: absolute;
+ top: 7px; left: -35px;
+ width: 29px; height: 30px;
+ background: url("../images/icon-arrow-choice.png") scroll 0 no-repeat;
+ background-size: contain;
+}
+@media only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) {
+ .imagify-recommend:before {
+ background-image: url("../images/icon-arrow-choice.svg");
+ }
+}
+
+/* One Time Adjustments */
+.imagify-offer-line[class*="imagify-onetime-"] {
+ padding: 0;
+ margin: .3em 0 0;
+}
+.imagify-offer-line.imagify-offer-line[class*="imagify-onetime-"]:first-child {
+ margin-top: 2em;
+}
+.imagify-offer-line[class*="imagify-onetime-"] + .imagify-offer-line {
+ margin-top: .5em;
+}
+.imagify-offer-selected.imagify-offer-line[class*="imagify-onetime-"] {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ border-width: 2px;
+}
+
+/* cols */
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-col-details {
+ position: relative;
+ overflow: hidden;
+ width: 21%;
+ background: #1F2332;
+ color: #FFF;
+}
+.imagify-offer-selected.imagify-offer-line[class*="imagify-onetime-"] .imagify-col-details {
+ background: #338EA6;
+}
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-col-details:before {
+ content: "";
+ position: absolute;
+ bottom: 0; right: 25px;
+ width: 75px; height: 54px;
+ background: url("../images/icon-pack.png");
+}
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-col-other-actions {
+ width: 30%;
+}
+
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-offer-size,
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-approx {
+ color: #FFF;
+}
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-offer-size {
+ font-size: 24px;
+}
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-approx {
+ font-size: 12px;
+}
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-price-block {
+ padding-left: 10px;
+}
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-dollars {
+ vertical-align: middle;
+}
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-price-big {
+ vertical-align: -5px;
+}
+.imagify-offer-line[class*="imagify-onetime-"] .imagify-price-mini {
+ vertical-align: 7px;
+}
+
+/* Simple Tabs */
+.imagify-tabs {
+ margin-bottom: 0;
+ list-style: none;
+ background: #E5EBEF;
+}
+.imagify-modal-content .imagify-tabs {
+ margin: 1em 0 0;
+}
+.imagify-tab {
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+ width: 50%;
+ margin: 0;
+ font-size: 23px;
+}
+.imagify-tab a {
+ display: block;
+ padding: 15px 10px;
+ color: inherit;
+ text-decoration: none;
+}
+.imagify-tab a:focus {
+ box-shadow: none;
+ outline: none;
+ color: #40B1D8;
+}
+.imagify-tab.imagify-current a {
+ background: #F6F7FB;
+}
+.imagify-tab-content.imagify-current {
+ display: block;
+}
+.imagify-tab-content {
+ display: none;
+}
+.imagify-tab-content .imagify-modal-section:first-child {
+ margin-top: 0;
+}
+
+/* Modal sidebar */
+.imagify-modal-sidebar-content,
+.imagify-payment-modal .imagify-modal-sidebar {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+}
+.imagify-modal-sidebar-content {
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+}
+p.imagify-modal-sidebar-title.imagify-modal-sidebar-title {
+ margin-top: 5px;
+ padding-right: 40px;
+ font-size: 18px;
+ color: #FFF;
+}
+.imagify-modal-testimony {
+ margin-top: 1em;
+}
+.imagify-modal-testimony + .imagify-modal-testimony {
+ margin-top: 2em;
+}
+@media (max-height:620px) {
+ .imagify-modal-testimony + .imagify-modal-testimony {
+ display: none;
+ }
+}
+.imagify-modal-testimony-person {
+ display: table;
+ width: 100%;
+}
+.imagify-modal-testimony-person > * {
+ display: table-cell;
+ vertical-align: middle;
+}
+.imagify-modal-avatar {
+ width: 114px;
+ line-height: 0;
+}
+.imagify-modal-avatar img {
+ border: 2px solid #FFF;
+ border-radius: 50%;
+ width: 96px; height: 96px;
+}
+.imagify-modal-identity a {
+ text-decoration: none;
+ font-weight: bold;
+}
+.imagify-modal-identity a:first-child {
+ font-size: 13px;
+}
+.imagify-modal-identity a:first-child + a {
+ display: block;
+ font-size: 10px;
+ color: #7A8996;
+}
+.imagify-modal-testimony-content.imagify-modal-testimony-content p {
+ font-size: 13px;
+ font-style: italic;
+ line-height: 1.7;
+ color: #7A8996;
+}
+.imagify-modal-sidebar-trust {
+ margin-top: auto;
+ padding-top: 1.5em;
+}
+.imagify-modal-sidebar-trust p {
+ margin: 0;
+ font-weight: bold;
+ font-size: 12px;
+ line-height: 1.7;
+}
+.imagify-modal-sidebar-trust p img {
+ margin-right: 3px;
+ vertical-align: -2px;
+}
+.imagify-modal-sidebar-trust p + p {
+ font-size: 11px;
+}
+
+/* Cart */
+.imagify-cart {
+ text-align: left;
+}
+.imagify-cart .imagify-cart-list {
+ border-top: 1px solid rgba(122, 137, 150, .2);
+ border-bottom: 1px solid rgba(122, 137, 150, .2);
+}
+.imagify-cart .imagify-cart-label {
+ margin-bottom: 0.5em;
+ font-size: 10px;
+ color: #2E3243;
+}
+.imagify-cart-list p {
+ margin: 0;
+ font-weight: bold;
+}
+.imagify-cart-item {
+ margin: .4em 0;
+}
+.imagify-cart .imagify-cart-suggestion {
+ margin-top: -.3em;
+}
+.imagify-cart-suggestion a,
+.imagify-cl-description p {
+ font-size: 10px;
+}
+.imagify-remove-from-cart {
+ border: 0;
+ padding: 0;
+ width: 14px;
+ height: 14px;
+ line-height: 13px;
+ border-radius: 50%;
+ background: #40B1D0;
+ cursor: pointer;
+ transition: background .3s;
+}
+.imagify-remove-from-cart i:before {
+ position: relative;
+ top: -6px; left: -3px;
+ font-size: 13px;
+ color: #FFF;
+}
+.imagify-remove-from-cart:hover,
+.imagify-remove-from-cart:focus {
+ background: #D0021B;
+}
+
+/* col sizes */
+.imagify-cart .imagify-cl-remove {
+ -webkit-box-flex: 0;
+ -webkit-flex-grow: 0;
+ -ms-flex-positive: 0;
+ flex-grow: 0;
+ width: 45px;
+}
+.imagify-cart .imagify-cl-name {
+ -webkit-box-flex: 0;
+ -webkit-flex-grow: 0;
+ -ms-flex-positive: 0;
+ flex-grow: 0;
+ width: 200px;
+}
+.imagify-cart .imagify-cl-description {
+ -webkit-align-self: flex-start;
+ -ms-flex-item-align: start;
+ align-self: flex-start;
+ padding-top: 10px;
+}
+.imagify-cart .imagify-cl-price {
+ text-align: right;
+}
+
+#imagify-payment-iframe {
+ width: 980px;
+ height: 672px;
+ background: #f6f7fb url(../images/loader-balls.svg) 50% 50% no-repeat;
+}
+
+.imagify-success-view {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ height: 100%;
+}
+.imagify-success-view p {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+/* Imagify cart item removing */
+.imagify-cart-emptied-item {
+ margin: .3em auto;
+ padding: 6px 20px;
+ background: #E6EBEF;
+ border-radius: 20px;
+}
+.imagify-cart-emptied-item.imagify-cart-emptied-item p {
+ font-weight: bold;
+}
+.imagify-cart-emptied-item a {
+ color: #40b1d0;
+ float: right;
+ font-weight: bold;
+}
diff --git a/wp-content/plugins/imagify/assets/css/pricing-modal.min.css b/wp-content/plugins/imagify/assets/css/pricing-modal.min.css
new file mode 100644
index 00000000..3079907a
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/pricing-modal.min.css
@@ -0,0 +1 @@
+.imagify-border-styled,.imagify-flex-table,.imagify-modal-cols,.imagify-offer-header,.imagify-payment-modal .imagify-modal-content,.imagify-tabs{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.imagify-border-styled,.imagify-modal-cols{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.imagify-payment-modal{text-align:center;color:#7A8996}.imagify-payment-modal *{-webkit-box-sizing:border-box;box-sizing:border-box}.imagify-modal-loader{position:absolute;top:0;left:0;right:0;bottom:0;background:url(../images/loader-balls.svg) center no-repeat #fff;z-index:10}.imagify-payment-modal .imagify-modal-content{width:980px;max-width:100%;min-width:925px;padding:0}.imagify-modal-content.imagify-iframe-viewing{width:980px;height:672px;overflow:hidden}.imagify-iframe-viewing #imagify-payment-process-view{width:980px;height:668px}.imagify-payment-modal .imagify-modal-main{width:70%}.imagify-iframe-viewing .imagify-modal-main{width:auto}.imagify-payment-modal .imagify-modal-content.imagify-success-viewing{min-width:auto;width:450px;min-height:300px}.imagify-success-viewing .imagify-modal-main{width:100%}.imagify-payment-modal .imagify-modal-sidebar{width:30%;padding:15px 20px;background:#1F2332;color:#FFF}.imagify-modal-content.imagify-iframe-viewing .imagify-modal-sidebar,.imagify-modal-content.imagify-success-viewing .imagify-modal-sidebar{display:none}.imagify-modal-section{padding:0 25px}.imagify-modal-section.section-gray{margin:0 0 1em;padding:10px 25px 15px;background:#F6F7FB}.imagify-tabs-contents .section-gray{padding:8px 25px 10px}.imagify-modal-section .imagify-modal-title:first-child{margin-top:1em;margin-bottom:1.5em}.imagify-modal-section.section-gray .imagify-modal-title{margin-top:.5em;margin-bottom:.5em}.imagify-modal-title{font-size:1.8em}.imagify-modal-title .imagify-inner-sub-title{display:block;font-size:.56em}.imagify-analyzing .imagify-numbers-calc,.imagify-modal-section .imagify-loader,.imagify-modal-section.imagify-analyzing .imagify-modal-cols,.imagify-numbers-notcalc{display:none}.imagify-analyzing .imagify-numbers-notcalc,.imagify-modal-section.imagify-analyzing .imagify-loader{display:block}.imagify-modal-section .imagify-loader{margin:2em auto}.imagify-border-styled{width:200px;margin:0 auto;color:#8BC34A;font-weight:700;font-size:.925em}.imagify-big-number,.imagify-payment-modal strong{font-weight:700;color:#4A4A4A}.imagify-border-styled:after,.imagify-border-styled:before{content:"";height:1px;background:rgba(0,0,0,.1);-ms-flex-preferred-size:40px;flex-basis:40px}.imagify-border-styled:before{margin-right:5px}.imagify-border-styled:after{margin-left:5px}.imagify-big-number{font-size:3.7em;margin:-3px 0;line-height:1}.imagify-popin-message{padding:5px 15px;text-align:left}.imagify-popin-message.imagify-error p{color:#FFF}.imagify-small-options{width:300px;margin:1em auto .5em;background:#338EA6;border-radius:4px}.imagify-small-options input[type=radio]:checked+label,.imagify-small-options input[type=radio]:not(:checked)+label{padding:8px 10px;font-size:13px;color:#FFF;-webkit-box-shadow:none;box-shadow:none;border-left:0}.imagify-small-options input[type=radio]:not(:checked)+label{background:#338EA6;color:rgba(255,255,255,.4)}.imagify-small-options input[type=radio]:checked+label{background:#40B1D0}.imagify-cols:after{content:"";display:table;clear:both}.js .imagify-iframe-viewing .close-btn{display:none}.imagify-modal .imagify-cols{padding:0 20px}.imagify-payment-modal .imagify-iconed{margin:1.5em 5em 1.5em 0}.imagify-iconed{position:relative;text-align:left;padding-left:42px;margin-right:15px;font-weight:500}.imagify-iconed .dashicons,.imagify-iconed .icon{position:absolute;font-size:2em;left:0;top:2px;color:#40B1D0}.imagify-payment-modal .close-btn{top:10px;right:10px;width:24px;height:24px;padding:2px 0 0 4.5px;color:#FFF;background:#40B1D0;border-radius:50%;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-payment-modal .close-btn i{margin-left:-3.5px;margin-top:-.5px}.imagify-payment-modal .close-btn:hover{background:#F6F7FB}.imagify-offer-line{margin-top:1.5em}.imagify-offer-line+.imagify-offer-line{margin-top:.75em}.imagify-offer-header{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 0 0 15px;border-radius:4px 4px 0 0;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-offer-header.imagify-offer-header.imagify-offer-header .imagify-inline-options label:last-child{border-radius:0 4px 0 0}.imagify-offer-header .imagify-inline-options{width:auto}.imagify-offer-title{font-weight:700;margin:0}.imagify-offer-header,.imagify-offer-header .imagify-inline-options input[type=radio]:not(:checked)+label{background:#E5EBEF}.imagify-offer-onetime .imagify-offer-header{padding-top:8px;padding-bottom:8px}.imagify-offer-onetimes>div{padding-top:15px;padding-bottom:15px}.imagify-offer-header .imagify-inline-options input[type=radio]:checked+label,.imagify-offer-header .imagify-inline-options input[type=radio]:not(:checked)+label{position:relative;padding:7px 30px;font-size:1em;letter-spacing:.05em;color:inherit;-webkit-box-shadow:0 0 0;box-shadow:0 0 0;border-radius:0}.imagify-offer-header .imagify-inline-options input[type=radio]:checked+label{background:#F6F7FB}.imagify-2-free{position:absolute;bottom:100%;left:0;right:0;padding:2px 10px;margin-bottom:8px;font-size:.8em;letter-spacing:0;text-transform:none;text-align:center;color:#FFF;background:#10121A;border-radius:2px}.imagify-2-free:after{content:"";position:absolute;left:50%;bottom:-3px;margin-left:-3px;border-top:3px solid #10121A;border-left:3px solid transparent;border-right:3px solid transparent}.imagify-2-free.imagify-b-right{bottom:auto;left:100%;right:-100%;margin-bottom:0;margin-left:8px}.imagify-2-free.imagify-b-right:after{left:-3px;bottom:auto;top:50%;margin-top:-3px;margin-left:0;border-right:3px solid #10121A;border-top:3px solid transparent;border-bottom:3px solid transparent;border-left:0}.imagify-2-free.imagify-b-bottom{bottom:-100%;left:0;right:0;margin-top:8px}.imagify-2-free.imagify-b-bottom:after{top:-3px;bottom:auto;border-bottom:3px solid #10121A;border-left:3px solid transparent;border-right:3px solid transparent;border-top:0}.imagify-offer-content{text-align:left;background:#F6F7FB;border-radius:0 0 4px 4px;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-offer-onetime .imagify-offer-content{padding:10px 0}div.imagify-col-checkbox{position:relative;width:25.5%;padding-top:10px;padding-bottom:7px}.imagify-col-checkbox label{display:block;padding-left:55px!important}.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:checked,.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:not(:checked){position:absolute;top:50%;left:6px;margin:-8px 0 0}.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:checked+label:before,.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:not(:checked)+label:before{margin:0;top:-2px;left:6px;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}.imagify-offer-by,.imagify-offer-size{-webkit-transition:all .275s;-o-transition:all .275s}.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:checked+label:after,.imagify-col-checkbox .imagify-checkbox.imagify-checkbox:not(:checked)+label:after{top:1px;left:13px}.imagify-offer-size{font-size:30px;color:#2E3243;font-weight:700;transition:all .275s}.imagify-offer-by{font-size:10px;transition:all .275s}.imagify-approx{display:none;font-size:11px;line-height:1.2;-webkit-transition:all .275s;-o-transition:all .275s;transition:all .275s}div.imagify-col-price{width:35%}.imagify-flex-table .imagify-price-block{padding-left:0;padding-right:0}.imagify-offer-monthlies .imagify-price-block,.imagify-offer-monthly .imagify-flex-table .imagify-price-block{padding-top:0}.imagify-flex-table .imagify-price-complement{padding-right:0;font-size:10px;font-weight:700}.imagify-price-block,.imagify-price-discount{white-space:nowrap}.imagify-price-block span,.imagify-price-discount span{display:inline-block;vertical-align:middle}.imagify-price-discount.imagify-price-discount{position:relative;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;padding-top:15px;font-weight:700;width:70px}.imagify-price-discount:before{content:"";position:absolute;top:25px;width:62%;height:2px;background:#2E3243;-webkit-transform:rotate(-15deg);-ms-transform:rotate(-15deg);transform:rotate(-15deg)}.imagify-offer-onetimes .imagify-price-discount:before{width:100%}.imagify-price-discount-dollar{color:#2E3243}.imagify-price-discount-number{color:#8BA6B4}.imagify-offer-selected .imagify-price-discount-number{color:#FFF}span.imagify-dollars{color:#1F2332;font-size:18px;font-weight:700;vertical-align:-2px}.imagify-offer-onetime .imagify-col-price{padding-top:0}.imagify-offer-onetime .imagify-dollars{vertical-align:-1px}.imagify-price-big,.imagify-price-mini{color:#40B1D0;font-weight:700}.imagify-price-big{font-size:36px}span.imagify-price-mini{font-size:18px;vertical-align:2px}span.imagify-price-by{font-size:10px;color:#1F2332;vertical-align:-13px;text-indent:-27px}.imagify-offer-selected,.imagify-offer-selected .imagify-checkbox.imagify-checkbox:checked+label:after,.imagify-offer-selected .imagify-checkbox.imagify-checkbox:not(:checked)+label:after,.imagify-offer-selected .imagify-col-other-actions a,.imagify-offer-selected .imagify-offer-size,.imagify-offer-selected .imagify-offer-title,.imagify-offer-selected .imagify-price-big,.imagify-offer-selected .imagify-price-complement,.imagify-offer-selected .imagify-price-mini{color:#FFF}.imagify-col-other-actions{width:18.5%;text-align:right}.imagify-col-other-actions a{font-size:11px}.imagify-offer-selected .imagify-offer-header,.imagify-offer-selected .imagify-offer-header .imagify-inline-options input[type=radio]:not(:checked)+label{background:#338EA6}.imagify-offer-selected .imagify-offer-content,.imagify-offer-selected .imagify-offer-header .imagify-inline-options input[type=radio]:checked+label{background:#40B1D0}.imagify-offer-selected .imagify-checkbox.imagify-checkbox:checked+label:before,.imagify-offer-selected .imagify-checkbox.imagify-checkbox:not(:checked)+label:before{border-color:#FFF;background:#40B1D0}.imagify-offer-selected .imagify-offer-by{color:#2E3243}.imagify-enough-free .imagify-not-enough-title,.imagify-enough-title{display:none}.imagify-enough-free .imagify-enough-title{display:block}.imagify-submit-line{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:2em 0;text-align:left}.imagify-coupon-section{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.imagify-coupon-section p{margin:0;line-height:1.3}.imagify-coupon-text{width:200px;max-width:100%;padding-right:15px}.imagify-coupon-loader{display:none}.imagify-coupon-text.checking{text-align:right}.imagify-coupon-text.checking .imagify-coupon-loader{display:inline}.imagify-coupon-text.checking label{display:none}.imagify-coupon-input{position:relative}.imagify-coupon-input input{position:relative;z-index:1}[id=imagify-coupon-validate].button-secondary{position:absolute;top:1px;right:3px;bottom:2px;-webkit-box-shadow:none;box-shadow:none;padding:4px 10px;z-index:0;-webkit-transition:-webkit-transform .275s;-o-transition:transform .275s;transition:transform .275s;transition:transform .275s,-webkit-transform .275s}.imagify-canbe-validate [id=imagify-coupon-validate]{-webkit-transform:translateX(45px);-ms-transform:translateX(45px);transform:translateX(45px)}.imagify-modal-section+.imagify-modal-promotion{margin-top:-1em}.imagify-modal-promotion{position:relative;overflow:hidden;display:none;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:15px 25px;background:#604D90;text-shadow:0 0 3px rgba(0,0,0,.3)}.imagify-modal-promotion.active{display:-webkit-box;display:-ms-flexbox;display:flex}[id=imagify-pricing-tab-onetime] .imagify-modal-promotion{margin-bottom:4em}.imagify-modal-promotion:before{content:"\f488";position:absolute;top:28px;left:8%;font-family:dashicons;font-size:90px;color:#8476A9;text-shadow:none}.imagify-modal-promotion p{position:relative;margin:.2em 0;color:#FFF}.imagify-promo-title{-ms-flex-preferred-size:100%;flex-basis:100%;text-transform:uppercase;font-size:20px;font-weight:700;letter-spacing:.125em}.imagify-until-date{-ms-flex-preferred-size:200px;flex-basis:200px;text-align:right}.imagify-until-date strong{color:#FFF}.imagify-submit-line button{font-size:16px}input.imagify-coupon-code{padding:10px;border:2px solid #7A8996;font-size:.875em;font-weight:700;border-radius:3px}.validated.imagify-coupon-section .imagify-coupon-text,.validated.imagify-coupon-section strong{color:#8BC34A}.validated.imagify-coupon-section .imagify-coupon-code{color:#8BC34A;border-color:#8BC34A}.invalid.imagify-coupon-section .imagify-coupon-text,.invalid.imagify-coupon-section strong{color:#d0021b}.invalid.imagify-coupon-section .imagify-coupon-code{color:#d0021b;border-color:#d0021b}.imagify-footer-lines{width:500px;max-width:100%;margin:2em auto 2.5em;font-size:.85em;line-height:1.5}.imagify-year-selected .imagify-switch-my .imagify-yearly{display:block}.imagify-month-selected .imagify-switch-my .imagify-yearly,.imagify-year-selected .imagify-switch-my .imagify-monthly{display:none}.imagify-month-selected .imagify-switch-my .imagify-monthly{display:block}.imagify-flex-table{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.imagify-flex-table>*{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;padding:7px 15px}div.imagify-col-details{width:22%;padding-left:25px}.imagify-col-details p{margin:0}.imagify-pricing-table{margin:0 20px}.imagify-pricing-table .imagify-offer-line{padding:.6em 0;border:2px solid #E8EEF0;text-align:left;border-radius:3px}.imagify-pricing-table .imagify-offer-line:first-child{margin-top:.75em}.imagify-pricing-table .imagify-offer-line.imagify-offer-selected:first-child{margin-top:1.75em}.imagify-pricing-table .imagify-offer-line+.imagify-offer-line{margin-top:-2px}.imagify-pricing-table .imagify-col-other-actions{width:20.5%}.imagify-pricing-table .imagify-approx{margin-left:0;line-height:.5;margin-bottom:1em}.imagify-pricing-table .imagify-offer-selected{-webkit-transform:scale(1.03);-ms-transform:scale(1.03);transform:scale(1.03);background:#40B1D0;border-width:0}.imagify-pricing-table .imagify-offer-selected .imagify-approx{color:#FFF}.imagify-pricing-table .imagify-button-secondary{padding:3px 20px;-webkit-box-shadow:none;box-shadow:none;text-transform:uppercase;font-size:12px;letter-spacing:.025em}.imagify-offer-selected.imagify-offer-selected .imagify-button-secondary{border:2px solid #FFF;background:#40B1D0;-webkit-box-shadow:none;box-shadow:none;text-shadow:none!important}.imagify-offer-selected.imagify-offer-selected .imagify-button-secondary:focus,.imagify-offer-selected.imagify-offer-selected .imagify-button-secondary:hover{background:#FFF;color:#40B1D0}.imagify-col .imagify-special-needs{margin-left:25px}.imagify-special-needs strong{font-size:25px;font-weight:700;color:#40B1D0}.imagify-special-needs span{display:block;font-size:12px;margin-top:-.5em}div.imagify-col-price{position:relative}.imagify-recommend{display:none;position:absolute;left:-20px;bottom:100%;padding:0;margin-bottom:8px;color:#1F2332;font-weight:700;font-style:italic}.imagify-offer-selected .imagify-recommend,.imagify-tab a,.imagify-tab-content.imagify-current{display:block}[class*=imagify-onetime-] .imagify-recommend{left:65px;margin-bottom:20px}.imagify-recommend:before{content:"";position:absolute;top:7px;left:-35px;width:29px;height:30px;background:url(../images/icon-arrow-choice.png) 0 no-repeat;background-size:contain}@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (-o-min-device-pixel-ratio:2/1),only screen and (min-resolution:192dpi),only screen and (min-resolution:2dppx){.imagify-recommend:before{background-image:url(../images/icon-arrow-choice.svg)}}.imagify-offer-line[class*=imagify-onetime-]{padding:0;margin:.3em 0 0}.imagify-offer-line.imagify-offer-line[class*=imagify-onetime-]:first-child{margin-top:2em}.imagify-offer-line[class*=imagify-onetime-]+.imagify-offer-line{margin-top:.5em}.imagify-offer-selected.imagify-offer-line[class*=imagify-onetime-]{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1);border-width:2px}.imagify-offer-line[class*=imagify-onetime-] .imagify-col-details{position:relative;overflow:hidden;width:21%;background:#1F2332;color:#FFF}.imagify-offer-selected.imagify-offer-line[class*=imagify-onetime-] .imagify-col-details{background:#338EA6}.imagify-offer-line[class*=imagify-onetime-] .imagify-col-details:before{content:"";position:absolute;bottom:0;right:25px;width:75px;height:54px;background:url(../images/icon-pack.png)}.imagify-offer-line[class*=imagify-onetime-] .imagify-col-other-actions{width:30%}.imagify-offer-line[class*=imagify-onetime-] .imagify-approx,.imagify-offer-line[class*=imagify-onetime-] .imagify-offer-size{color:#FFF}.imagify-offer-line[class*=imagify-onetime-] .imagify-offer-size{font-size:24px}.imagify-offer-line[class*=imagify-onetime-] .imagify-approx{font-size:12px}.imagify-offer-line[class*=imagify-onetime-] .imagify-price-block{padding-left:10px}.imagify-offer-line[class*=imagify-onetime-] .imagify-dollars{vertical-align:middle}.imagify-offer-line[class*=imagify-onetime-] .imagify-price-big{vertical-align:-5px}.imagify-offer-line[class*=imagify-onetime-] .imagify-price-mini{vertical-align:7px}.imagify-tabs{margin-bottom:0;list-style:none;background:#E5EBEF}.imagify-modal-content .imagify-tabs{margin:1em 0 0}.imagify-tab{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;width:50%;margin:0;font-size:23px}.imagify-tab a{padding:15px 10px;color:inherit;text-decoration:none}.imagify-tab a:focus{-webkit-box-shadow:none;box-shadow:none;outline:0;color:#40B1D8}.imagify-tab.imagify-current a{background:#F6F7FB}.imagify-tab-content{display:none}.imagify-tab-content .imagify-modal-section:first-child{margin-top:0}.imagify-modal-sidebar-content,.imagify-payment-modal .imagify-modal-sidebar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.imagify-modal-sidebar-content{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}p.imagify-modal-sidebar-title.imagify-modal-sidebar-title{margin-top:5px;padding-right:40px;font-size:18px;color:#FFF}.imagify-modal-testimony{margin-top:1em}.imagify-modal-testimony+.imagify-modal-testimony{margin-top:2em}@media (max-height:620px){.imagify-modal-testimony+.imagify-modal-testimony{display:none}}.imagify-modal-testimony-person{display:table;width:100%}.imagify-modal-testimony-person>*{display:table-cell;vertical-align:middle}.imagify-modal-avatar{width:114px;line-height:0}.imagify-modal-avatar img{border:2px solid #FFF;border-radius:50%;width:96px;height:96px}.imagify-modal-identity a{text-decoration:none;font-weight:700}.imagify-modal-identity a:first-child{font-size:13px}.imagify-modal-identity a:first-child+a{display:block;font-size:10px;color:#7A8996}.imagify-modal-testimony-content.imagify-modal-testimony-content p{font-size:13px;font-style:italic;line-height:1.7;color:#7A8996}.imagify-modal-sidebar-trust{margin-top:auto;padding-top:1.5em}.imagify-modal-sidebar-trust p{margin:0;font-weight:700;font-size:12px;line-height:1.7}.imagify-modal-sidebar-trust p img{margin-right:3px;vertical-align:-2px}.imagify-modal-sidebar-trust p+p{font-size:11px}.imagify-cart{text-align:left}.imagify-cart .imagify-cart-list{border-top:1px solid rgba(122,137,150,.2);border-bottom:1px solid rgba(122,137,150,.2)}.imagify-cart .imagify-cart-label{margin-bottom:.5em;font-size:10px;color:#2E3243}.imagify-cart-list p{margin:0;font-weight:700}.imagify-cart-item{margin:.4em 0}.imagify-cart .imagify-cart-suggestion{margin-top:-.3em}.imagify-cart-suggestion a,.imagify-cl-description p{font-size:10px}.imagify-remove-from-cart{border:0;padding:0;width:14px;height:14px;line-height:13px;border-radius:50%;background:#40B1D0;cursor:pointer;-webkit-transition:background .3s;-o-transition:background .3s;transition:background .3s}.imagify-remove-from-cart i:before{position:relative;top:-6px;left:-3px;font-size:13px;color:#FFF}.imagify-remove-from-cart:focus,.imagify-remove-from-cart:hover{background:#D0021B}.imagify-cart .imagify-cl-remove{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:45px}.imagify-cart .imagify-cl-name{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:200px}.imagify-cart .imagify-cl-description{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start;padding-top:10px}.imagify-cart .imagify-cl-price{text-align:right}#imagify-payment-iframe{width:980px;height:672px;background:url(../images/loader-balls.svg) 50% 50% no-repeat #f6f7fb}.imagify-success-view{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;height:100%}.imagify-success-view p{font-weight:700;font-size:16px}.imagify-cart-emptied-item{margin:.3em auto;padding:6px 20px;background:#E6EBEF;border-radius:20px}.imagify-cart-emptied-item.imagify-cart-emptied-item p{font-weight:700}.imagify-cart-emptied-item a{color:#40b1d0;float:right;font-weight:700}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/sweetalert-custom.css b/wp-content/plugins/imagify/assets/css/sweetalert-custom.css
new file mode 100644
index 00000000..c0fd8a3b
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/sweetalert-custom.css
@@ -0,0 +1,169 @@
+/* Sub Layer */
+body[class*="_imagify"] .swal2-container.swal2-shown {
+ background: rgb(31, 35 ,50);
+ background: rgba(31, 35 ,50, .9);
+ z-index: 100000;
+}
+
+/* White Container */
+.imagify-sweet-alert .swal2-modal {
+ border-radius: 2px;
+}
+
+/* To get icon background dark */
+.imagify-sweet-alert {
+ background: #1F2332!important;
+}
+.imagify-sweet-alert .swal2-icon {
+ margin-bottom: 5px;
+}
+
+/* header error color */
+.imagify-swal-error-header {
+ background: #C51162!important;
+}
+.imagify-swal-error-header .swal2-icon {
+ border-color: #FFF;
+ color: #FFF;
+}
+
+/* Title and Subtitle */
+.imagify-sweet-alert .swal2-title {
+ margin: 0;
+ padding: 28px 32px;
+ font-size: 24px;
+ text-align: center;
+ color: #FFF;
+ background: #1F2332;
+}
+.imagify-swal-has-subtitle .swal2-title {
+ text-align: left;
+}
+.imagify-swal-error-header .swal2-title {
+ background: #C51162;
+ text-align: center;
+}
+.imagify-sweet-alert .imagify-swal-subtitle {
+ padding: 0 32px 28px;
+ margin-top: -16px;
+ font-weight: 500;
+ font-size: 14px;
+ text-align: left;
+ color: #7A8996;
+ background: #1F2332;
+}
+.imagify-swal-error-header .imagify-swal-subtitle {
+ color: #FFF;
+ background: #C51162;
+ text-align: center;
+}
+
+/* Buttons */
+.imagify-sweet-alert .swal2-buttonswrapper,
+.imagify-swal-buttonswrapper {
+ margin-top: 0;
+ padding: 22px;
+ background: #F4F7F9;
+}
+.imagify-sweet-alert button.swal2-styled,
+.imagify-swal-buttonswrapper a.button.imagify-button-primary {
+ height: auto;
+ padding: 12px 32px;
+ margin: 10px;
+ font-size: 14px;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ border-radius: 3px;
+ background-color: #40b1d0 !important;
+ text-shadow: none!important;
+ box-shadow: 0 3px 0 #338ea6;
+}
+.imagify-swal-buttonswrapper a.button.imagify-button-primary:focus,
+.imagify-swal-buttonswrapper a.button.imagify-button-primary:hover {
+ text-shadow: none;
+ color: #FFF;
+}
+.imagify-swal-buttonswrapper a.button svg {
+ margin-right: 12px;
+ vertical-align: -2px;
+}
+.imagify-sweet-alert button.loading {
+ border-radius: 100% !important;
+ height: 40px !important;
+ padding:0!important;
+ box-shadow: none!important;
+}
+.imagify-sweet-alert button.swal2-cancel {
+ color: #7A8996;
+ background: #E9EFF2 !important;
+ box-shadow: 0 3px 0 rgba(31, 35, 50, .2);
+}
+.imagify-sweet-alert-signup.imagify-sweet-alert {
+ background: #FFF!important;
+}
+.imagify-sweet-alert-signup .swal2-buttonswrapper {
+ padding: 12px 22px;
+}
+.swal2-success-circular-line-left,
+.swal2-success-fix,
+.swal2-success-circular-line-right {
+ background: #1F2332 !important
+}
+.imagify-sweet-alert-signup .sa-confirm-button-container {
+ width: 40%;
+}
+.imagify-sweet-alert-signup .swal2-input {
+ margin-top: 0;
+ margin-left: 40px;
+ margin-right: 40px;
+ width: calc( 100% - 80px);
+}
+.imagify-sweet-alert .sa-input-error:before,
+.imagify-sweet-alert .sa-input-error:after,
+.imagify-sweet-alert .la-ball-fall {
+ top: 25% !important;
+}
+
+.imagify-sweet-alert .swal2-buttonswrapper.swal2-loading .swal2-confirm.swal2-confirm {
+ height: 40px !important;
+ border-radius: 100% !important;
+ border-left-width: 0 !important;
+ border-right-width: 0 !important;
+}
+
+/* Imagify swal contents */
+.imagify-sweet-alert .swal2-content {
+ padding: 28px 32px;
+ background: #FFF;
+}
+.imagify-swal-has-subtitle .swal2-content {
+ padding: 0;
+}
+.imagify-swal-content {
+ font-size: 14px;
+ padding: 28px 32px;
+}
+
+/* Quota */
+.imagify-swal-quota .imagify-space-left {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 4px 32px;
+ text-align: left;
+ font-weight: bold;
+ color: #FFF;
+ background: #343A49;
+}
+.imagify-swal-quota .imagify-space-left p {
+ font-size: 14px;
+}
+.imagify-swal-quota .imagify-space-left [class^="imagify-bar-"] {
+ width: auto;
+ flex-basis: 269px;
+}
+
+/* Close button */
+.imagify-sweet-alert .swal2-close {
+ color: rgba(255,255,255,.5);
+}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/sweetalert-custom.min.css b/wp-content/plugins/imagify/assets/css/sweetalert-custom.min.css
new file mode 100644
index 00000000..89beaf26
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/sweetalert-custom.min.css
@@ -0,0 +1 @@
+body[class*="_imagify"] .swal2-container.swal2-shown{background:rgb(31,35 ,50);background:rgba(31,35 ,50,.9);z-index:100000}.imagify-sweet-alert .swal2-modal{border-radius:2px}.imagify-sweet-alert{background:#1F2332!important}.imagify-sweet-alert .swal2-icon{margin-bottom:5px}.imagify-swal-error-header{background:#C51162!important}.imagify-swal-error-header .swal2-icon{border-color:#FFF;color:#FFF}.imagify-sweet-alert .swal2-title{margin:0;padding:28px 32px;font-size:24px;text-align:center;color:#FFF;background:#1F2332}.imagify-swal-has-subtitle .swal2-title{text-align:left}.imagify-swal-error-header .swal2-title{background:#C51162;text-align:center}.imagify-sweet-alert .imagify-swal-subtitle{padding:0 32px 28px;margin-top:-16px;font-weight:500;font-size:14px;text-align:left;color:#7A8996;background:#1F2332}.imagify-swal-error-header .imagify-swal-subtitle{color:#FFF;background:#C51162;text-align:center}.imagify-swal-buttonswrapper,.imagify-sweet-alert .swal2-buttonswrapper{margin-top:0;padding:22px;background:#F4F7F9}.imagify-swal-buttonswrapper a.button.imagify-button-primary,.imagify-sweet-alert button.swal2-styled{height:auto;padding:12px 32px;margin:10px;font-size:14px;letter-spacing:1px;text-transform:uppercase;border-radius:3px;background-color:#40b1d0!important;text-shadow:none!important;-webkit-box-shadow:0 3px 0 #338ea6;box-shadow:0 3px 0 #338ea6}.imagify-swal-buttonswrapper a.button.imagify-button-primary:focus,.imagify-swal-buttonswrapper a.button.imagify-button-primary:hover{text-shadow:none;color:#FFF}.imagify-swal-buttonswrapper a.button svg{margin-right:12px;vertical-align:-2px}.imagify-sweet-alert button.loading{border-radius:100%!important;height:40px!important;padding:0!important;-webkit-box-shadow:none!important;box-shadow:none!important}.imagify-sweet-alert button.swal2-cancel{color:#7A8996;background:#E9EFF2!important;-webkit-box-shadow:0 3px 0 rgba(31,35,50,.2);box-shadow:0 3px 0 rgba(31,35,50,.2)}.imagify-sweet-alert-signup.imagify-sweet-alert{background:#FFF!important}.imagify-sweet-alert-signup .swal2-buttonswrapper{padding:12px 22px}.swal2-success-circular-line-left,.swal2-success-circular-line-right,.swal2-success-fix{background:#1F2332!important}.imagify-sweet-alert-signup .sa-confirm-button-container{width:40%}.imagify-sweet-alert-signup .swal2-input{margin-top:0;margin-left:40px;margin-right:40px;width:calc(100% - 80px)}.imagify-sweet-alert .la-ball-fall,.imagify-sweet-alert .sa-input-error:after,.imagify-sweet-alert .sa-input-error:before{top:25%!important}.imagify-sweet-alert .swal2-buttonswrapper.swal2-loading .swal2-confirm.swal2-confirm{height:40px!important;border-radius:100%!important;border-left-width:0!important;border-right-width:0!important}.imagify-sweet-alert .swal2-content{padding:28px 32px;background:#FFF}.imagify-swal-has-subtitle .swal2-content{padding:0}.imagify-swal-content{font-size:14px;padding:28px 32px}.imagify-swal-quota .imagify-space-left{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:4px 32px;text-align:left;font-weight:700;color:#FFF;background:#343A49}.imagify-swal-quota .imagify-space-left p{font-size:14px}.imagify-swal-quota .imagify-space-left [class^=imagify-bar-]{width:auto;-ms-flex-preferred-size:269px;flex-basis:269px}.imagify-sweet-alert .swal2-close{color:rgba(255,255,255,.5)}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/sweetalert2.css b/wp-content/plugins/imagify/assets/css/sweetalert2.css
new file mode 100644
index 00000000..10a2ca1e
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/sweetalert2.css
@@ -0,0 +1,716 @@
+body.swal2-shown {
+ overflow-y: hidden; }
+
+body.swal2-iosfix {
+ position: fixed;
+ left: 0;
+ right: 0; }
+
+.swal2-container {
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ padding: 10px;
+ background-color: transparent;
+ z-index: 1060; }
+ .swal2-container.swal2-fade {
+ -webkit-transition: background-color .1s;
+ transition: background-color .1s; }
+ .swal2-container.swal2-shown {
+ background-color: rgba(0, 0, 0, 0.4); }
+
+.swal2-modal {
+ background-color: #fff;
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ border-radius: 5px;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ text-align: center;
+ margin: auto;
+ overflow-x: hidden;
+ overflow-y: auto;
+ display: none;
+ position: relative;
+ max-width: 100%; }
+ .swal2-modal:focus {
+ outline: none; }
+ .swal2-modal.swal2-loading {
+ overflow-y: hidden; }
+ .swal2-modal .swal2-title {
+ color: #595959;
+ font-size: 30px;
+ text-align: center;
+ font-weight: 600;
+ text-transform: none;
+ position: relative;
+ margin: 0 0 .4em;
+ padding: 0;
+ display: block;
+ word-wrap: break-word; }
+ .swal2-modal .swal2-buttonswrapper {
+ margin-top: 15px; }
+ .swal2-modal .swal2-buttonswrapper:not(.swal2-loading) .swal2-styled[disabled] {
+ opacity: .4;
+ cursor: no-drop; }
+ .swal2-modal .swal2-buttonswrapper.swal2-loading .swal2-styled.swal2-confirm {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ border: 4px solid transparent;
+ border-color: transparent;
+ width: 40px;
+ height: 40px;
+ padding: 0;
+ margin: 7.5px;
+ vertical-align: top;
+ background-color: transparent !important;
+ color: transparent;
+ cursor: default;
+ border-radius: 100%;
+ -webkit-animation: rotate-loading 1.5s linear 0s infinite normal;
+ animation: rotate-loading 1.5s linear 0s infinite normal;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none; }
+ .swal2-modal .swal2-buttonswrapper.swal2-loading .swal2-styled.swal2-cancel {
+ margin-left: 30px;
+ margin-right: 30px; }
+ .swal2-modal .swal2-buttonswrapper.swal2-loading :not(.swal2-styled).swal2-confirm::after {
+ display: inline-block;
+ content: '';
+ margin-left: 5px 0 15px;
+ vertical-align: -1px;
+ height: 15px;
+ width: 15px;
+ border: 3px solid #999999;
+ -webkit-box-shadow: 1px 1px 1px #fff;
+ box-shadow: 1px 1px 1px #fff;
+ border-right-color: transparent;
+ border-radius: 50%;
+ -webkit-animation: rotate-loading 1.5s linear 0s infinite normal;
+ animation: rotate-loading 1.5s linear 0s infinite normal; }
+ .swal2-modal .swal2-styled {
+ border: 0;
+ border-radius: 3px;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ color: #fff;
+ cursor: pointer;
+ font-size: 17px;
+ font-weight: 500;
+ margin: 15px 5px 0;
+ padding: 10px 32px; }
+ .swal2-modal .swal2-image {
+ margin: 20px auto;
+ max-width: 100%; }
+ .swal2-modal .swal2-close {
+ background: transparent;
+ border: 0;
+ margin: 0;
+ padding: 0;
+ width: 38px;
+ height: 40px;
+ font-size: 36px;
+ line-height: 40px;
+ font-family: serif;
+ position: absolute;
+ top: 5px;
+ right: 8px;
+ cursor: pointer;
+ color: #cccccc;
+ -webkit-transition: color .1s ease;
+ transition: color .1s ease; }
+ .swal2-modal .swal2-close:hover {
+ color: #d55; }
+ .swal2-modal > .swal2-input,
+ .swal2-modal > .swal2-file,
+ .swal2-modal > .swal2-textarea,
+ .swal2-modal > .swal2-select,
+ .swal2-modal > .swal2-radio,
+ .swal2-modal > .swal2-checkbox {
+ display: none; }
+ .swal2-modal .swal2-content {
+ font-size: 18px;
+ text-align: center;
+ font-weight: 300;
+ position: relative;
+ float: none;
+ margin: 0;
+ padding: 0;
+ line-height: normal;
+ color: #545454;
+ word-wrap: break-word; }
+ .swal2-modal .swal2-input,
+ .swal2-modal .swal2-file,
+ .swal2-modal .swal2-textarea,
+ .swal2-modal .swal2-select,
+ .swal2-modal .swal2-radio,
+ .swal2-modal .swal2-checkbox {
+ margin: 20px auto; }
+ .swal2-modal .swal2-input,
+ .swal2-modal .swal2-file,
+ .swal2-modal .swal2-textarea {
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ font-size: 18px;
+ border-radius: 3px;
+ border: 1px solid #d9d9d9;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.06);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.06);
+ -webkit-transition: border-color box-shadow .3s;
+ transition: border-color box-shadow .3s; }
+ .swal2-modal .swal2-input.swal2-inputerror,
+ .swal2-modal .swal2-file.swal2-inputerror,
+ .swal2-modal .swal2-textarea.swal2-inputerror {
+ border-color: #f27474 !important;
+ -webkit-box-shadow: 0 0 2px #f27474 !important;
+ box-shadow: 0 0 2px #f27474 !important; }
+ .swal2-modal .swal2-input:focus,
+ .swal2-modal .swal2-file:focus,
+ .swal2-modal .swal2-textarea:focus {
+ outline: none;
+ border: 1px solid #b4dbed;
+ -webkit-box-shadow: 0 0 3px #c4e6f5;
+ box-shadow: 0 0 3px #c4e6f5; }
+ .swal2-modal .swal2-input:focus::-webkit-input-placeholder,
+ .swal2-modal .swal2-file:focus::-webkit-input-placeholder,
+ .swal2-modal .swal2-textarea:focus::-webkit-input-placeholder {
+ -webkit-transition: opacity .3s .03s ease;
+ transition: opacity .3s .03s ease;
+ opacity: .8; }
+ .swal2-modal .swal2-input:focus:-ms-input-placeholder,
+ .swal2-modal .swal2-file:focus:-ms-input-placeholder,
+ .swal2-modal .swal2-textarea:focus:-ms-input-placeholder {
+ -webkit-transition: opacity .3s .03s ease;
+ transition: opacity .3s .03s ease;
+ opacity: .8; }
+ .swal2-modal .swal2-input:focus::placeholder,
+ .swal2-modal .swal2-file:focus::placeholder,
+ .swal2-modal .swal2-textarea:focus::placeholder {
+ -webkit-transition: opacity .3s .03s ease;
+ transition: opacity .3s .03s ease;
+ opacity: .8; }
+ .swal2-modal .swal2-input::-webkit-input-placeholder,
+ .swal2-modal .swal2-file::-webkit-input-placeholder,
+ .swal2-modal .swal2-textarea::-webkit-input-placeholder {
+ color: #e6e6e6; }
+ .swal2-modal .swal2-input:-ms-input-placeholder,
+ .swal2-modal .swal2-file:-ms-input-placeholder,
+ .swal2-modal .swal2-textarea:-ms-input-placeholder {
+ color: #e6e6e6; }
+ .swal2-modal .swal2-input::placeholder,
+ .swal2-modal .swal2-file::placeholder,
+ .swal2-modal .swal2-textarea::placeholder {
+ color: #e6e6e6; }
+ .swal2-modal .swal2-range input {
+ float: left;
+ width: 80%; }
+ .swal2-modal .swal2-range output {
+ float: right;
+ width: 20%;
+ font-size: 20px;
+ font-weight: 600;
+ text-align: center; }
+ .swal2-modal .swal2-range input,
+ .swal2-modal .swal2-range output {
+ height: 43px;
+ line-height: 43px;
+ vertical-align: middle;
+ margin: 20px auto;
+ padding: 0; }
+ .swal2-modal .swal2-input {
+ height: 43px;
+ padding: 0 12px; }
+ .swal2-modal .swal2-input[type='number'] {
+ max-width: 150px; }
+ .swal2-modal .swal2-file {
+ font-size: 20px; }
+ .swal2-modal .swal2-textarea {
+ height: 108px;
+ padding: 12px; }
+ .swal2-modal .swal2-select {
+ color: #545454;
+ font-size: inherit;
+ padding: 5px 10px;
+ min-width: 40%;
+ max-width: 100%; }
+ .swal2-modal .swal2-radio {
+ border: 0; }
+ .swal2-modal .swal2-radio label:not(:first-child) {
+ margin-left: 20px; }
+ .swal2-modal .swal2-radio input,
+ .swal2-modal .swal2-radio span {
+ vertical-align: middle; }
+ .swal2-modal .swal2-radio input {
+ margin: 0 3px 0 0; }
+ .swal2-modal .swal2-checkbox {
+ color: #545454; }
+ .swal2-modal .swal2-checkbox input,
+ .swal2-modal .swal2-checkbox span {
+ vertical-align: middle; }
+ .swal2-modal .swal2-validationerror {
+ background-color: #f0f0f0;
+ margin: 0 -20px;
+ overflow: hidden;
+ padding: 10px;
+ color: gray;
+ font-size: 16px;
+ font-weight: 300;
+ display: none; }
+ .swal2-modal .swal2-validationerror::before {
+ content: '!';
+ display: inline-block;
+ width: 24px;
+ height: 24px;
+ border-radius: 50%;
+ background-color: #ea7d7d;
+ color: #fff;
+ line-height: 24px;
+ text-align: center;
+ margin-right: 10px; }
+
+@supports (-ms-accelerator: true) {
+ .swal2-range input {
+ width: 100% !important; }
+ .swal2-range output {
+ display: none; } }
+
+@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+ .swal2-range input {
+ width: 100% !important; }
+ .swal2-range output {
+ display: none; } }
+
+.swal2-icon {
+ width: 80px;
+ height: 80px;
+ border: 4px solid transparent;
+ border-radius: 50%;
+ margin: 20px auto 30px;
+ padding: 0;
+ position: relative;
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+ cursor: default;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none; }
+ .swal2-icon.swal2-error {
+ border-color: #f27474; }
+ .swal2-icon.swal2-error .swal2-x-mark {
+ position: relative;
+ display: block; }
+ .swal2-icon.swal2-error [class^='swal2-x-mark-line'] {
+ position: absolute;
+ height: 5px;
+ width: 47px;
+ background-color: #f27474;
+ display: block;
+ top: 37px;
+ border-radius: 2px; }
+ .swal2-icon.swal2-error [class^='swal2-x-mark-line'][class$='left'] {
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+ left: 17px; }
+ .swal2-icon.swal2-error [class^='swal2-x-mark-line'][class$='right'] {
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ right: 16px; }
+ .swal2-icon.swal2-warning {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ color: #f8bb86;
+ border-color: #facea8;
+ font-size: 60px;
+ line-height: 80px;
+ text-align: center; }
+ .swal2-icon.swal2-info {
+ font-family: 'Open Sans', sans-serif;
+ color: #3fc3ee;
+ border-color: #9de0f6;
+ font-size: 60px;
+ line-height: 80px;
+ text-align: center; }
+ .swal2-icon.swal2-question {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ color: #87adbd;
+ border-color: #c9dae1;
+ font-size: 60px;
+ line-height: 80px;
+ text-align: center; }
+ .swal2-icon.swal2-success {
+ border-color: #a5dc86; }
+ .swal2-icon.swal2-success [class^='swal2-success-circular-line'] {
+ border-radius: 50%;
+ position: absolute;
+ width: 60px;
+ height: 120px;
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg); }
+ .swal2-icon.swal2-success [class^='swal2-success-circular-line'][class$='left'] {
+ border-radius: 120px 0 0 120px;
+ top: -7px;
+ left: -33px;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ -webkit-transform-origin: 60px 60px;
+ transform-origin: 60px 60px; }
+ .swal2-icon.swal2-success [class^='swal2-success-circular-line'][class$='right'] {
+ border-radius: 0 120px 120px 0;
+ top: -11px;
+ left: 30px;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ -webkit-transform-origin: 0 60px;
+ transform-origin: 0 60px; }
+ .swal2-icon.swal2-success .swal2-success-ring {
+ width: 80px;
+ height: 80px;
+ border: 4px solid rgba(165, 220, 134, 0.2);
+ border-radius: 50%;
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+ position: absolute;
+ left: -4px;
+ top: -4px;
+ z-index: 2; }
+ .swal2-icon.swal2-success .swal2-success-fix {
+ width: 7px;
+ height: 90px;
+ position: absolute;
+ left: 28px;
+ top: 8px;
+ z-index: 1;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg); }
+ .swal2-icon.swal2-success [class^='swal2-success-line'] {
+ height: 5px;
+ background-color: #a5dc86;
+ display: block;
+ border-radius: 2px;
+ position: absolute;
+ z-index: 2; }
+ .swal2-icon.swal2-success [class^='swal2-success-line'][class$='tip'] {
+ width: 25px;
+ left: 14px;
+ top: 46px;
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg); }
+ .swal2-icon.swal2-success [class^='swal2-success-line'][class$='long'] {
+ width: 47px;
+ right: 8px;
+ top: 38px;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg); }
+
+.swal2-progresssteps {
+ font-weight: 600;
+ margin: 0 0 20px;
+ padding: 0; }
+ .swal2-progresssteps li {
+ display: inline-block;
+ position: relative; }
+ .swal2-progresssteps .swal2-progresscircle {
+ background: #3085d6;
+ border-radius: 2em;
+ color: #fff;
+ height: 2em;
+ line-height: 2em;
+ text-align: center;
+ width: 2em;
+ z-index: 20; }
+ .swal2-progresssteps .swal2-progresscircle:first-child {
+ margin-left: 0; }
+ .swal2-progresssteps .swal2-progresscircle:last-child {
+ margin-right: 0; }
+ .swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep {
+ background: #3085d6; }
+ .swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep ~ .swal2-progresscircle {
+ background: #add8e6; }
+ .swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep ~ .swal2-progressline {
+ background: #add8e6; }
+ .swal2-progresssteps .swal2-progressline {
+ background: #3085d6;
+ height: .4em;
+ margin: 0 -1px;
+ z-index: 10; }
+
+[class^='swal2'] {
+ -webkit-tap-highlight-color: transparent; }
+
+@-webkit-keyframes showSweetAlert {
+ 0% {
+ -webkit-transform: scale(0.7);
+ transform: scale(0.7); }
+ 45% {
+ -webkit-transform: scale(1.05);
+ transform: scale(1.05); }
+ 80% {
+ -webkit-transform: scale(0.95);
+ transform: scale(0.95); }
+ 100% {
+ -webkit-transform: scale(1);
+ transform: scale(1); } }
+
+@keyframes showSweetAlert {
+ 0% {
+ -webkit-transform: scale(0.7);
+ transform: scale(0.7); }
+ 45% {
+ -webkit-transform: scale(1.05);
+ transform: scale(1.05); }
+ 80% {
+ -webkit-transform: scale(0.95);
+ transform: scale(0.95); }
+ 100% {
+ -webkit-transform: scale(1);
+ transform: scale(1); } }
+
+@-webkit-keyframes hideSweetAlert {
+ 0% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ opacity: 1; }
+ 100% {
+ -webkit-transform: scale(0.5);
+ transform: scale(0.5);
+ opacity: 0; } }
+
+@keyframes hideSweetAlert {
+ 0% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ opacity: 1; }
+ 100% {
+ -webkit-transform: scale(0.5);
+ transform: scale(0.5);
+ opacity: 0; } }
+
+.swal2-show {
+ -webkit-animation: showSweetAlert 0.3s;
+ animation: showSweetAlert 0.3s; }
+ .swal2-show.swal2-noanimation {
+ -webkit-animation: none;
+ animation: none; }
+
+.swal2-hide {
+ -webkit-animation: hideSweetAlert 0.15s forwards;
+ animation: hideSweetAlert 0.15s forwards; }
+ .swal2-hide.swal2-noanimation {
+ -webkit-animation: none;
+ animation: none; }
+
+@-webkit-keyframes animate-success-tip {
+ 0% {
+ width: 0;
+ left: 1px;
+ top: 19px; }
+ 54% {
+ width: 0;
+ left: 1px;
+ top: 19px; }
+ 70% {
+ width: 50px;
+ left: -8px;
+ top: 37px; }
+ 84% {
+ width: 17px;
+ left: 21px;
+ top: 48px; }
+ 100% {
+ width: 25px;
+ left: 14px;
+ top: 45px; } }
+
+@keyframes animate-success-tip {
+ 0% {
+ width: 0;
+ left: 1px;
+ top: 19px; }
+ 54% {
+ width: 0;
+ left: 1px;
+ top: 19px; }
+ 70% {
+ width: 50px;
+ left: -8px;
+ top: 37px; }
+ 84% {
+ width: 17px;
+ left: 21px;
+ top: 48px; }
+ 100% {
+ width: 25px;
+ left: 14px;
+ top: 45px; } }
+
+@-webkit-keyframes animate-success-long {
+ 0% {
+ width: 0;
+ right: 46px;
+ top: 54px; }
+ 65% {
+ width: 0;
+ right: 46px;
+ top: 54px; }
+ 84% {
+ width: 55px;
+ right: 0;
+ top: 35px; }
+ 100% {
+ width: 47px;
+ right: 8px;
+ top: 38px; } }
+
+@keyframes animate-success-long {
+ 0% {
+ width: 0;
+ right: 46px;
+ top: 54px; }
+ 65% {
+ width: 0;
+ right: 46px;
+ top: 54px; }
+ 84% {
+ width: 55px;
+ right: 0;
+ top: 35px; }
+ 100% {
+ width: 47px;
+ right: 8px;
+ top: 38px; } }
+
+@-webkit-keyframes rotatePlaceholder {
+ 0% {
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg); }
+ 5% {
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg); }
+ 12% {
+ -webkit-transform: rotate(-405deg);
+ transform: rotate(-405deg); }
+ 100% {
+ -webkit-transform: rotate(-405deg);
+ transform: rotate(-405deg); } }
+
+@keyframes rotatePlaceholder {
+ 0% {
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg); }
+ 5% {
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg); }
+ 12% {
+ -webkit-transform: rotate(-405deg);
+ transform: rotate(-405deg); }
+ 100% {
+ -webkit-transform: rotate(-405deg);
+ transform: rotate(-405deg); } }
+
+.swal2-animate-success-line-tip {
+ -webkit-animation: animate-success-tip 0.75s;
+ animation: animate-success-tip 0.75s; }
+
+.swal2-animate-success-line-long {
+ -webkit-animation: animate-success-long 0.75s;
+ animation: animate-success-long 0.75s; }
+
+.swal2-success.swal2-animate-success-icon .swal2-success-circular-line-right {
+ -webkit-animation: rotatePlaceholder 4.25s ease-in;
+ animation: rotatePlaceholder 4.25s ease-in; }
+
+@-webkit-keyframes animate-error-icon {
+ 0% {
+ -webkit-transform: rotateX(100deg);
+ transform: rotateX(100deg);
+ opacity: 0; }
+ 100% {
+ -webkit-transform: rotateX(0deg);
+ transform: rotateX(0deg);
+ opacity: 1; } }
+
+@keyframes animate-error-icon {
+ 0% {
+ -webkit-transform: rotateX(100deg);
+ transform: rotateX(100deg);
+ opacity: 0; }
+ 100% {
+ -webkit-transform: rotateX(0deg);
+ transform: rotateX(0deg);
+ opacity: 1; } }
+
+.swal2-animate-error-icon {
+ -webkit-animation: animate-error-icon 0.5s;
+ animation: animate-error-icon 0.5s; }
+
+@-webkit-keyframes animate-x-mark {
+ 0% {
+ -webkit-transform: scale(0.4);
+ transform: scale(0.4);
+ margin-top: 26px;
+ opacity: 0; }
+ 50% {
+ -webkit-transform: scale(0.4);
+ transform: scale(0.4);
+ margin-top: 26px;
+ opacity: 0; }
+ 80% {
+ -webkit-transform: scale(1.15);
+ transform: scale(1.15);
+ margin-top: -6px; }
+ 100% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ margin-top: 0;
+ opacity: 1; } }
+
+@keyframes animate-x-mark {
+ 0% {
+ -webkit-transform: scale(0.4);
+ transform: scale(0.4);
+ margin-top: 26px;
+ opacity: 0; }
+ 50% {
+ -webkit-transform: scale(0.4);
+ transform: scale(0.4);
+ margin-top: 26px;
+ opacity: 0; }
+ 80% {
+ -webkit-transform: scale(1.15);
+ transform: scale(1.15);
+ margin-top: -6px; }
+ 100% {
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ margin-top: 0;
+ opacity: 1; } }
+
+.swal2-animate-x-mark {
+ -webkit-animation: animate-x-mark 0.5s;
+ animation: animate-x-mark 0.5s; }
+
+@-webkit-keyframes rotate-loading {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg); }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg); } }
+
+@keyframes rotate-loading {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg); }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg); } }
diff --git a/wp-content/plugins/imagify/assets/css/sweetalert2.min.css b/wp-content/plugins/imagify/assets/css/sweetalert2.min.css
new file mode 100644
index 00000000..80f952a3
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/sweetalert2.min.css
@@ -0,0 +1 @@
+body.swal2-shown{overflow-y:hidden}body.swal2-iosfix{position:fixed;left:0;right:0}.swal2-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;left:0;bottom:0;right:0;padding:10px;background-color:transparent;z-index:1060}.swal2-container.swal2-fade{-webkit-transition:background-color .1s;-o-transition:background-color .1s;transition:background-color .1s}.swal2-container.swal2-shown{background-color:rgba(0,0,0,.4)}.swal2-modal{background-color:#fff;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border-radius:5px;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;margin:auto;overflow-x:hidden;overflow-y:auto;display:none;position:relative;max-width:100%}.swal2-modal:focus{outline:0}.swal2-modal.swal2-loading{overflow-y:hidden}.swal2-modal .swal2-title{color:#595959;font-size:30px;text-align:center;font-weight:600;text-transform:none;position:relative;margin:0 0 .4em;padding:0;display:block;word-wrap:break-word}.swal2-modal .swal2-buttonswrapper{margin-top:15px}.swal2-modal .swal2-buttonswrapper:not(.swal2-loading) .swal2-styled[disabled]{opacity:.4;cursor:no-drop}.swal2-modal .swal2-buttonswrapper.swal2-loading .swal2-styled.swal2-confirm{-webkit-box-sizing:border-box;box-sizing:border-box;border:4px solid transparent;border-color:transparent;width:40px;height:40px;padding:0;margin:7.5px;vertical-align:top;background-color:transparent!important;color:transparent;cursor:default;border-radius:100%;-webkit-animation:rotate-loading 1.5s linear 0s infinite normal;animation:rotate-loading 1.5s linear 0s infinite normal;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-modal .swal2-buttonswrapper.swal2-loading .swal2-styled.swal2-cancel{margin-left:30px;margin-right:30px}.swal2-modal .swal2-buttonswrapper.swal2-loading :not(.swal2-styled).swal2-confirm::after{display:inline-block;content:'';margin-left:5px 0 15px;vertical-align:-1px;height:15px;width:15px;border:3px solid #999;-webkit-box-shadow:1px 1px 1px #fff;box-shadow:1px 1px 1px #fff;border-right-color:transparent;border-radius:50%;-webkit-animation:rotate-loading 1.5s linear 0s infinite normal;animation:rotate-loading 1.5s linear 0s infinite normal}.swal2-modal .swal2-styled{border:0;border-radius:3px;-webkit-box-shadow:none;box-shadow:none;color:#fff;cursor:pointer;font-size:17px;font-weight:500;margin:15px 5px 0;padding:10px 32px}.swal2-modal .swal2-image{margin:20px auto;max-width:100%}.swal2-modal .swal2-close{background:0 0;border:0;margin:0;padding:0;width:38px;height:40px;font-size:36px;line-height:40px;font-family:serif;position:absolute;top:5px;right:8px;cursor:pointer;color:#ccc;-webkit-transition:color .1s ease;-o-transition:color .1s ease;transition:color .1s ease}.swal2-modal .swal2-close:hover{color:#d55}.swal2-modal>.swal2-checkbox,.swal2-modal>.swal2-file,.swal2-modal>.swal2-input,.swal2-modal>.swal2-radio,.swal2-modal>.swal2-select,.swal2-modal>.swal2-textarea{display:none}.swal2-modal .swal2-content{font-size:18px;text-align:center;font-weight:300;position:relative;float:none;margin:0;padding:0;line-height:normal;color:#545454;word-wrap:break-word}.swal2-modal .swal2-checkbox,.swal2-modal .swal2-file,.swal2-modal .swal2-input,.swal2-modal .swal2-radio,.swal2-modal .swal2-select,.swal2-modal .swal2-textarea{margin:20px auto}.swal2-modal .swal2-file,.swal2-modal .swal2-input,.swal2-modal .swal2-textarea{width:100%;-webkit-box-sizing:border-box;box-sizing:border-box;font-size:18px;border-radius:3px;border:1px solid #d9d9d9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.06);box-shadow:inset 0 1px 1px rgba(0,0,0,.06);-webkit-transition:border-color box-shadow .3s;-o-transition:border-color box-shadow .3s;transition:border-color box-shadow .3s}.swal2-modal .swal2-file.swal2-inputerror,.swal2-modal .swal2-input.swal2-inputerror,.swal2-modal .swal2-textarea.swal2-inputerror{border-color:#f27474!important;-webkit-box-shadow:0 0 2px #f27474!important;box-shadow:0 0 2px #f27474!important}.swal2-modal .swal2-file:focus,.swal2-modal .swal2-input:focus,.swal2-modal .swal2-textarea:focus{outline:0;border:1px solid #b4dbed;-webkit-box-shadow:0 0 3px #c4e6f5;box-shadow:0 0 3px #c4e6f5}.swal2-modal .swal2-file:focus::-webkit-input-placeholder,.swal2-modal .swal2-input:focus::-webkit-input-placeholder,.swal2-modal .swal2-textarea:focus::-webkit-input-placeholder{-webkit-transition:opacity .3s .03s ease;-o-transition:opacity .3s .03s ease;transition:opacity .3s .03s ease;opacity:.8}.swal2-modal .swal2-file:focus:-ms-input-placeholder,.swal2-modal .swal2-input:focus:-ms-input-placeholder,.swal2-modal .swal2-textarea:focus:-ms-input-placeholder{-webkit-transition:opacity .3s .03s ease;-o-transition:opacity .3s .03s ease;transition:opacity .3s .03s ease;opacity:.8}.swal2-modal .swal2-file:focus::-moz-placeholder,.swal2-modal .swal2-input:focus::-moz-placeholder,.swal2-modal .swal2-textarea:focus::-moz-placeholder{-webkit-transition:opacity .3s .03s ease;-o-transition:opacity .3s .03s ease;transition:opacity .3s .03s ease;opacity:.8}.swal2-modal .swal2-file:focus::-ms-input-placeholder,.swal2-modal .swal2-input:focus::-ms-input-placeholder,.swal2-modal .swal2-textarea:focus::-ms-input-placeholder{-webkit-transition:opacity .3s .03s ease;-o-transition:opacity .3s .03s ease;transition:opacity .3s .03s ease;opacity:.8}.swal2-modal .swal2-file:focus::placeholder,.swal2-modal .swal2-input:focus::placeholder,.swal2-modal .swal2-textarea:focus::placeholder{-webkit-transition:opacity .3s .03s ease;-o-transition:opacity .3s .03s ease;transition:opacity .3s .03s ease;opacity:.8}.swal2-modal .swal2-file::-webkit-input-placeholder,.swal2-modal .swal2-input::-webkit-input-placeholder,.swal2-modal .swal2-textarea::-webkit-input-placeholder{color:#e6e6e6}.swal2-modal .swal2-file:-ms-input-placeholder,.swal2-modal .swal2-input:-ms-input-placeholder,.swal2-modal .swal2-textarea:-ms-input-placeholder{color:#e6e6e6}.swal2-modal .swal2-file::-moz-placeholder,.swal2-modal .swal2-input::-moz-placeholder,.swal2-modal .swal2-textarea::-moz-placeholder{color:#e6e6e6}.swal2-modal .swal2-file::-ms-input-placeholder,.swal2-modal .swal2-input::-ms-input-placeholder,.swal2-modal .swal2-textarea::-ms-input-placeholder{color:#e6e6e6}.swal2-modal .swal2-file::placeholder,.swal2-modal .swal2-input::placeholder,.swal2-modal .swal2-textarea::placeholder{color:#e6e6e6}.swal2-modal .swal2-range input{float:left;width:80%}.swal2-modal .swal2-range output{float:right;width:20%;font-size:20px;font-weight:600;text-align:center}.swal2-modal .swal2-range input,.swal2-modal .swal2-range output{height:43px;line-height:43px;vertical-align:middle;margin:20px auto;padding:0}.swal2-modal .swal2-input{height:43px;padding:0 12px}.swal2-modal .swal2-input[type=number]{max-width:150px}.swal2-modal .swal2-file{font-size:20px}.swal2-modal .swal2-textarea{height:108px;padding:12px}.swal2-modal .swal2-select{color:#545454;font-size:inherit;padding:5px 10px;min-width:40%;max-width:100%}.swal2-modal .swal2-radio{border:0}.swal2-modal .swal2-radio label:not(:first-child){margin-left:20px}.swal2-modal .swal2-radio input,.swal2-modal .swal2-radio span{vertical-align:middle}.swal2-modal .swal2-radio input{margin:0 3px 0 0}.swal2-modal .swal2-checkbox{color:#545454}.swal2-modal .swal2-checkbox input,.swal2-modal .swal2-checkbox span{vertical-align:middle}.swal2-modal .swal2-validationerror{background-color:#f0f0f0;margin:0 -20px;overflow:hidden;padding:10px;color:gray;font-size:16px;font-weight:300;display:none}.swal2-modal .swal2-validationerror::before{content:'!';display:inline-block;width:24px;height:24px;border-radius:50%;background-color:#ea7d7d;color:#fff;line-height:24px;text-align:center;margin-right:10px}.swal2-icon.swal2-info,.swal2-icon.swal2-question,.swal2-icon.swal2-warning{font-size:60px;line-height:80px;text-align:center}@supports (-ms-accelerator:true){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-range input{width:100%!important}.swal2-range output{display:none}}.swal2-icon{width:80px;height:80px;border:4px solid transparent;border-radius:50%;margin:20px auto 30px;padding:0;position:relative;-webkit-box-sizing:content-box;box-sizing:content-box;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-icon.swal2-error{border-color:#f27474}.swal2-icon.swal2-error .swal2-x-mark{position:relative;display:block}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{position:absolute;height:5px;width:47px;background-color:#f27474;display:block;top:37px;border-radius:2px}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);left:17px}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg);right:16px}.swal2-icon.swal2-warning{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#f8bb86;border-color:#facea8}.swal2-icon.swal2-info{font-family:'Open Sans',sans-serif;color:#3fc3ee;border-color:#9de0f6}.swal2-icon.swal2-question{font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#87adbd;border-color:#c9dae1}.swal2-icon.swal2-success{border-color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-circular-line]{border-radius:50%;position:absolute;width:60px;height:120px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left]{border-radius:120px 0 0 120px;top:-7px;left:-33px;-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:60px 60px;-ms-transform-origin:60px 60px;transform-origin:60px 60px}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right]{border-radius:0 120px 120px 0;top:-11px;left:30px;-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:0 60px;-ms-transform-origin:0 60px;transform-origin:0 60px}.swal2-icon.swal2-success .swal2-success-ring{width:80px;height:80px;border:4px solid rgba(165,220,134,.2);border-radius:50%;-webkit-box-sizing:content-box;box-sizing:content-box;position:absolute;left:-4px;top:-4px;z-index:2}.swal2-icon.swal2-success .swal2-success-fix{width:7px;height:90px;position:absolute;left:28px;top:8px;z-index:1;-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.swal2-icon.swal2-success [class^=swal2-success-line]{height:5px;background-color:#a5dc86;display:block;border-radius:2px;position:absolute;z-index:2}.swal2-icon.swal2-success [class^=swal2-success-line][class$=tip]{width:25px;left:14px;top:46px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-line][class$=long]{width:47px;right:8px;top:38px;-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.swal2-progresssteps{font-weight:600;margin:0 0 20px;padding:0}.swal2-progresssteps li{display:inline-block;position:relative}.swal2-progresssteps .swal2-progresscircle{background:#3085d6;border-radius:2em;color:#fff;height:2em;line-height:2em;text-align:center;width:2em;z-index:20}.swal2-progresssteps .swal2-progresscircle:first-child{margin-left:0}.swal2-progresssteps .swal2-progresscircle:last-child{margin-right:0}.swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep{background:#3085d6}.swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep~.swal2-progresscircle,.swal2-progresssteps .swal2-progresscircle.swal2-activeprogressstep~.swal2-progressline{background:#add8e6}.swal2-progresssteps .swal2-progressline{background:#3085d6;height:.4em;margin:0 -1px;z-index:10}[class^=swal2]{-webkit-tap-highlight-color:transparent}@-webkit-keyframes showSweetAlert{0%{-webkit-transform:scale(.7);transform:scale(.7)}45%{-webkit-transform:scale(1.05);transform:scale(1.05)}80%{-webkit-transform:scale(.95);transform:scale(.95)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes showSweetAlert{0%{-webkit-transform:scale(.7);transform:scale(.7)}45%{-webkit-transform:scale(1.05);transform:scale(1.05)}80%{-webkit-transform:scale(.95);transform:scale(.95)}100%{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes hideSweetAlert{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}100%{-webkit-transform:scale(.5);transform:scale(.5);opacity:0}}@keyframes hideSweetAlert{0%{-webkit-transform:scale(1);transform:scale(1);opacity:1}100%{-webkit-transform:scale(.5);transform:scale(.5);opacity:0}}.swal2-show{-webkit-animation:showSweetAlert .3s;animation:showSweetAlert .3s}.swal2-show.swal2-noanimation{-webkit-animation:none;animation:none}.swal2-hide{-webkit-animation:hideSweetAlert .15s forwards;animation:hideSweetAlert .15s forwards}.swal2-hide.swal2-noanimation{-webkit-animation:none;animation:none}@-webkit-keyframes animate-success-tip{0%,54%{width:0;left:1px;top:19px}70%{width:50px;left:-8px;top:37px}84%{width:17px;left:21px;top:48px}100%{width:25px;left:14px;top:45px}}@keyframes animate-success-tip{0%,54%{width:0;left:1px;top:19px}70%{width:50px;left:-8px;top:37px}84%{width:17px;left:21px;top:48px}100%{width:25px;left:14px;top:45px}}@-webkit-keyframes animate-success-long{0%,65%{width:0;right:46px;top:54px}84%{width:55px;right:0;top:35px}100%{width:47px;right:8px;top:38px}}@keyframes animate-success-long{0%,65%{width:0;right:46px;top:54px}84%{width:55px;right:0;top:35px}100%{width:47px;right:8px;top:38px}}@-webkit-keyframes rotatePlaceholder{0%,5%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}100%,12%{-webkit-transform:rotate(-405deg);transform:rotate(-405deg)}}@keyframes rotatePlaceholder{0%,5%{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}100%,12%{-webkit-transform:rotate(-405deg);transform:rotate(-405deg)}}.swal2-animate-success-line-tip{-webkit-animation:animate-success-tip .75s;animation:animate-success-tip .75s}.swal2-animate-success-line-long{-webkit-animation:animate-success-long .75s;animation:animate-success-long .75s}.swal2-success.swal2-animate-success-icon .swal2-success-circular-line-right{-webkit-animation:rotatePlaceholder 4.25s ease-in;animation:rotatePlaceholder 4.25s ease-in}@-webkit-keyframes animate-error-icon{0%{-webkit-transform:rotateX(100deg);transform:rotateX(100deg);opacity:0}100%{-webkit-transform:rotateX(0);transform:rotateX(0);opacity:1}}@keyframes animate-error-icon{0%{-webkit-transform:rotateX(100deg);transform:rotateX(100deg);opacity:0}100%{-webkit-transform:rotateX(0);transform:rotateX(0);opacity:1}}.swal2-animate-error-icon{-webkit-animation:animate-error-icon .5s;animation:animate-error-icon .5s}@-webkit-keyframes animate-x-mark{0%,50%{-webkit-transform:scale(.4);transform:scale(.4);margin-top:26px;opacity:0}80%{-webkit-transform:scale(1.15);transform:scale(1.15);margin-top:-6px}100%{-webkit-transform:scale(1);transform:scale(1);margin-top:0;opacity:1}}@keyframes animate-x-mark{0%,50%{-webkit-transform:scale(.4);transform:scale(.4);margin-top:26px;opacity:0}80%{-webkit-transform:scale(1.15);transform:scale(1.15);margin-top:-6px}100%{-webkit-transform:scale(1);transform:scale(1);margin-top:0;opacity:1}}.swal2-animate-x-mark{-webkit-animation:animate-x-mark .5s;animation:animate-x-mark .5s}@-webkit-keyframes rotate-loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate-loading{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/css/twentytwenty.css b/wp-content/plugins/imagify/assets/css/twentytwenty.css
new file mode 100644
index 00000000..08357373
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/twentytwenty.css
@@ -0,0 +1,315 @@
+/**
+ * Twentwenty image comparison
+ */
+.twentytwenty-handle {
+ z-index: 40;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ height: 64px;
+ width: 64px;
+ margin-left: -32px;
+ margin-top: -32px;
+ border-radius: 50%;
+ box-shadow: 0 3px 0 #338EA6;
+ background: #40B1D0;
+ cursor: pointer;
+}
+.twentytwenty-horizontal .twentytwenty-handle:before,
+.twentytwenty-horizontal .twentytwenty-handle:after {
+ left: 50%;
+ width: 2px;
+ height: 9999px;
+ margin-left: -1px;
+}
+
+.twentytwenty-horizontal .twentytwenty-handle:before {
+ bottom: 50%;
+ margin-bottom: 32px;
+ box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+}
+
+.twentytwenty-horizontal .twentytwenty-handle:after {
+ top: 50%;
+ margin-top: 34px;
+ box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
+}
+
+.twentytwenty-horizontal .twentytwenty-handle:before,
+.twentytwenty-horizontal .twentytwenty-handle:after {
+ content: "";
+ position: absolute;
+ z-index: 30;
+ display: block;
+ background: #F2F5F7;
+ box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
+}
+
+.twentytwenty-labels,
+.twentytwenty-overlay {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ -webkit-transition-duration: 0.5s;
+ transition-duration: 0.5s;
+}
+
+.twentytwenty-labels {
+ opacity: 1;
+ -webkit-transition-property: opacity;
+ transition-property: opacity;
+}
+
+.twentytwenty-labels .twentytwenty-label-content {
+ position: absolute;
+ padding: 0 12px;
+ font-size: 13px;
+ letter-spacing: 0.1em;
+ line-height: 38px;
+ color: white;
+ background: #1F2332;
+ border-radius: 2px;
+}
+
+.twentytwenty-horizontal .twentytwenty-labels .twentytwenty-label-content {
+ bottom: 15px;
+}
+
+.twentytwenty-after-label .twentytwenty-label-content {
+ background: #40B1D0;
+}
+
+.twentytwenty-left-arrow,
+.twentytwenty-right-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border: 8px inset transparent;
+}
+
+.twentytwenty-left-arrow,
+.twentytwenty-right-arrow {
+ top: 50%;
+ margin-top: -8px;
+}
+
+.twentytwenty-container {
+ box-sizing: content-box;
+ position: relative;
+ z-index: 0;
+ overflow: hidden;
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.15);
+ opacity: 0;
+ -webkit-transition: opacity 0.4s;
+ transition: opacity 0.4s;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+}
+
+.twentytwenty-container * {
+ box-sizing: content-box;
+}
+
+.twentytwenty-container img {
+ position: absolute;
+ top: 0;
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.loaded .twentytwenty-container {
+ opacity: 1;
+}
+
+.twentytwenty-container.active .twentytwenty-overlay .twentytwenty-labels,
+.twentytwenty-container.active .twentytwenty-overlay:hover .twentytwenty-labels {
+ opacity: 0;
+}
+
+.twentytwenty-horizontal .twentytwenty-before-label .twentytwenty-label-content {
+ left: 15px;
+}
+
+.twentytwenty-horizontal .twentytwenty-after-label .twentytwenty-label-content {
+ right: 15px;
+}
+
+.twentytwenty-overlay {
+ z-index: 25;
+}
+.twentytwenty-before {
+ z-index: 20;
+}
+.twentytwenty-after {
+ z-index: 10;
+}
+
+/* Buttons for image choices */
+.twentytwenty-duo-buttons {
+ position: absolute;
+ top: 10px;
+ z-index: 30;
+ overflow: hidden;
+}
+
+.twentytwenty-duo-buttons button {
+ float: left;
+ padding: 2px 6px;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.125em;
+ font-weight: bold;
+ border: 0;
+ background: #1f2332;
+ color: #FFF;
+ transition: all .3s;
+ cursor: pointer;
+}
+
+.twentytwenty-duo-buttons button:hover,
+.twentytwenty-duo-buttons button:focus {
+ background: #444;
+}
+
+.twentytwenty-duo-buttons button:first-child {
+ border-radius: 3px 0 0 3px;
+}
+
+.twentytwenty-duo-buttons button:last-child {
+ border-radius: 0 3px 3px 0;
+}
+
+.twentytwenty-duo-buttons button.selected {
+ background: #8bc34a;
+ text-shadow: 0 0 1px rgba(0,0,0,.2);
+ cursor: default;
+}
+
+.twentytwenty-duo-left {
+ left: 10px;
+}
+
+.twentytwenty-duo-right {
+ right: 10px;
+}
+
+.twentytwenty-left-arrow {
+ left: 50%;
+ margin-left: -22px;
+ border-right: 8px solid white;
+}
+
+.twentytwenty-right-arrow {
+ right: 50%;
+ margin-right: -22px;
+ border-left: 8px solid white;
+}
+
+#imagify-visual-comparison .close-btn,
+.imagify-visual-comparison .close-btn {
+ top: 50px;
+ right: 5px;
+ width: 33px;
+ height: 33px;
+ padding: 1px 0 0 2px;
+ border: 1px solid #F2F2F2;
+ color: #F2F2F2;
+ line-height: 19px;
+ text-align: center;
+ border-radius: 50%;
+}
+
+.imagify-modal .imagify-comparison-title {
+ font-size: 28px;
+ margin-bottom: 1em;
+ color: #F2F2F2;
+ text-align: left;
+}
+.imagify-modal .imagify-comparison-title .twentytwenty-duo-buttons {
+ position: static;
+ margin: 0 10px 0 15px;
+}
+.imagify-comparison-title .twentytwenty-duo-buttons button {
+ float: none;
+ padding: 6px 12px;
+ font-size: 16px;
+ text-transform: none;
+ border: 1px solid #40B1D0;
+ color: #888899;
+ letter-spacing: 0;
+}
+.imagify-comparison-title .twentytwenty-duo-buttons button:focus {
+ outline: none;
+ box-shadow: none;
+}
+.imagify-comparison-title .twentytwenty-duo-buttons .selected {
+ border: 1px solid #40B1D0;
+ color: #FFF;
+ background: #40B1D0;
+}
+
+.imagify-comparison-levels {
+ margin: 15px 0;
+ overflow: hidden;
+}
+.imagify-comparison-levels .imagify-c-level {
+ display: none;
+ min-width: 175px;
+ font-size: 11px;
+}
+.imagify-c-level.go-left {
+ float: left;
+}
+.imagify-c-level.go-right {
+ float: right;
+}
+.imagify-c-level.go-right,
+.imagify-c-level.go-left {
+ display: table;
+}
+.imagify-c-level .imagify-c-level-row {
+ display: table-row;
+ margin: 0;
+ color: #FFF;
+}
+.imagify-c-level-row > span {
+ display: table-cell;
+ padding: 2px 0;
+}
+.imagify-c-level-row .value {
+ text-align: right;
+ padding-left: 5px;
+}
+.imagify-c-level-row .value.level {
+ color: #40b1d0;
+}
+.imagify-c-level-row .value.size {
+ color: #8bc34a;
+ font-weight: bold;
+}
+
+/* TT Loader */
+.imagify-modal .loader {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin: -32px 0 0 -32px;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity .4s;
+}
+.imagify-modal .loading .loader {
+ visibility: visible;
+ opacity: 1;
+}
+
+/* Specifics for too high modals */
+.modal-is-too-high .imagify-comparison-levels {
+ position: absolute;
+ padding: 15px 20px;
+ background: rgba(31, 35, 50, 0.95);
+ bottom: 0; left: 0; right: 0;
+ margin-bottom: 0;
+}
diff --git a/wp-content/plugins/imagify/assets/css/twentytwenty.min.css b/wp-content/plugins/imagify/assets/css/twentytwenty.min.css
new file mode 100644
index 00000000..c052b33e
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/css/twentytwenty.min.css
@@ -0,0 +1 @@
+.twentytwenty-handle{z-index:40;position:absolute;left:50%;top:50%;height:64px;width:64px;margin-left:-32px;margin-top:-32px;border-radius:50%;-webkit-box-shadow:0 3px 0 #338EA6;box-shadow:0 3px 0 #338EA6;background:#40B1D0;cursor:pointer}.twentytwenty-horizontal .twentytwenty-handle:after,.twentytwenty-horizontal .twentytwenty-handle:before{left:50%;width:2px;height:9999px;margin-left:-1px;content:"";position:absolute;z-index:30;display:block;background:#F2F5F7;-webkit-box-shadow:0 0 12px rgba(51,51,51,.5);box-shadow:0 0 12px rgba(51,51,51,.5)}.twentytwenty-horizontal .twentytwenty-handle:before{bottom:50%;margin-bottom:32px}.twentytwenty-horizontal .twentytwenty-handle:after{top:50%;margin-top:34px}.twentytwenty-labels,.twentytwenty-overlay{position:absolute;top:0;width:100%;height:100%;-webkit-transition-duration:.5s;-o-transition-duration:.5s;transition-duration:.5s}.twentytwenty-labels{opacity:1;-webkit-transition-property:opacity;-o-transition-property:opacity;transition-property:opacity}.twentytwenty-labels .twentytwenty-label-content{position:absolute;padding:0 12px;font-size:13px;letter-spacing:.1em;line-height:38px;color:#fff;background:#1F2332;border-radius:2px}.twentytwenty-horizontal .twentytwenty-labels .twentytwenty-label-content{bottom:15px}.twentytwenty-after-label .twentytwenty-label-content{background:#40B1D0}.twentytwenty-left-arrow,.twentytwenty-right-arrow{position:absolute;width:0;height:0;border:8px inset transparent;top:50%;margin-top:-8px}.twentytwenty-container{-webkit-box-sizing:content-box;box-sizing:content-box;position:relative;z-index:0;overflow:hidden;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.15);box-shadow:0 5px 10px rgba(0,0,0,.15);opacity:0;-webkit-transition:opacity .4s;-o-transition:opacity .4s;transition:opacity .4s;-webkit-user-select:none;-moz-user-select:none}.twentytwenty-container *{-webkit-box-sizing:content-box;box-sizing:content-box}.twentytwenty-container img{position:absolute;top:0;display:block;width:100%;height:auto}.loaded .twentytwenty-container{opacity:1}.twentytwenty-container.active .twentytwenty-overlay .twentytwenty-labels,.twentytwenty-container.active .twentytwenty-overlay:hover .twentytwenty-labels{opacity:0}.twentytwenty-horizontal .twentytwenty-before-label .twentytwenty-label-content{left:15px}.twentytwenty-horizontal .twentytwenty-after-label .twentytwenty-label-content{right:15px}.twentytwenty-overlay{z-index:25}.twentytwenty-before{z-index:20}.twentytwenty-after{z-index:10}.twentytwenty-duo-buttons{position:absolute;top:10px;z-index:30;overflow:hidden}.twentytwenty-duo-buttons button{float:left;padding:2px 6px;font-size:11px;text-transform:uppercase;letter-spacing:.125em;font-weight:700;border:0;background:#1f2332;color:#FFF;-webkit-transition:all .3s;-o-transition:all .3s;transition:all .3s;cursor:pointer}.twentytwenty-duo-buttons button:focus,.twentytwenty-duo-buttons button:hover{background:#444}.twentytwenty-duo-buttons button:first-child{border-radius:3px 0 0 3px}.twentytwenty-duo-buttons button:last-child{border-radius:0 3px 3px 0}.twentytwenty-duo-buttons button.selected{background:#8bc34a;text-shadow:0 0 1px rgba(0,0,0,.2);cursor:default}.twentytwenty-duo-left{left:10px}.twentytwenty-duo-right{right:10px}.twentytwenty-left-arrow{left:50%;margin-left:-22px;border-right:8px solid #fff}.twentytwenty-right-arrow{right:50%;margin-right:-22px;border-left:8px solid #fff}#imagify-visual-comparison .close-btn,.imagify-visual-comparison .close-btn{top:50px;right:5px;width:33px;height:33px;padding:1px 0 0 2px;border:1px solid #F2F2F2;color:#F2F2F2;line-height:19px;text-align:center;border-radius:50%}.imagify-modal .imagify-comparison-title{font-size:28px;margin-bottom:1em;color:#F2F2F2;text-align:left}.imagify-modal .imagify-comparison-title .twentytwenty-duo-buttons{position:static;margin:0 10px 0 15px}.imagify-comparison-title .twentytwenty-duo-buttons button{float:none;padding:6px 12px;font-size:16px;text-transform:none;border:1px solid #40B1D0;color:#889;letter-spacing:0}.imagify-comparison-title .twentytwenty-duo-buttons button:focus{outline:0;-webkit-box-shadow:none;box-shadow:none}.imagify-comparison-title .twentytwenty-duo-buttons .selected{border:1px solid #40B1D0;color:#FFF;background:#40B1D0}.imagify-comparison-levels{margin:15px 0;overflow:hidden}.imagify-comparison-levels .imagify-c-level{display:none;min-width:175px;font-size:11px}.imagify-c-level.go-left{float:left}.imagify-c-level.go-right{float:right}.imagify-c-level.go-left,.imagify-c-level.go-right{display:table}.imagify-c-level .imagify-c-level-row{display:table-row;margin:0;color:#FFF}.imagify-c-level-row>span{display:table-cell;padding:2px 0}.imagify-c-level-row .value{text-align:right;padding-left:5px}.imagify-c-level-row .value.level{color:#40b1d0}.imagify-c-level-row .value.size{color:#8bc34a;font-weight:700}.imagify-modal .loader{position:absolute;top:50%;left:50%;margin:-32px 0 0 -32px;opacity:0;visibility:hidden;-webkit-transition:opacity .4s;-o-transition:opacity .4s;transition:opacity .4s}.imagify-modal .loading .loader{visibility:visible;opacity:1}.modal-is-too-high .imagify-comparison-levels{position:absolute;padding:15px 20px;background:rgba(31,35,50,.95);bottom:0;left:0;right:0;margin-bottom:0}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/big-blue-check.png b/wp-content/plugins/imagify/assets/images/big-blue-check.png
new file mode 100644
index 00000000..d6bf18a3
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/big-blue-check.png differ
diff --git a/wp-content/plugins/imagify/assets/images/bulk.svg b/wp-content/plugins/imagify/assets/images/bulk.svg
new file mode 100644
index 00000000..ce0f684b
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/bulk.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/check-1.svg b/wp-content/plugins/imagify/assets/images/check-1.svg
new file mode 100644
index 00000000..0212af9d
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/check-1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/check-mini-1.svg b/wp-content/plugins/imagify/assets/images/check-mini-1.svg
new file mode 100644
index 00000000..8bcb9508
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/check-mini-1.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/assets/images/check-mini.svg b/wp-content/plugins/imagify/assets/images/check-mini.svg
new file mode 100644
index 00000000..042c07dd
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/check-mini.svg
@@ -0,0 +1,14 @@
+
+
+
+ Path
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/check.svg b/wp-content/plugins/imagify/assets/images/check.svg
new file mode 100644
index 00000000..a7772216
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/check.svg
@@ -0,0 +1,14 @@
+
+
+
+ Path
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/cloudy-sun.svg b/wp-content/plugins/imagify/assets/images/cloudy-sun.svg
new file mode 100644
index 00000000..fb059222
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/cloudy-sun.svg
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/assets/images/facebook_c.svg b/wp-content/plugins/imagify/assets/images/facebook_c.svg
new file mode 100644
index 00000000..81c578b7
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/facebook_c.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/gear.svg b/wp-content/plugins/imagify/assets/images/gear.svg
new file mode 100644
index 00000000..2746e8a9
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/gear.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-alert.svg b/wp-content/plugins/imagify/assets/images/icon-alert.svg
new file mode 100644
index 00000000..47c1605c
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-alert.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-arrow-choice.png b/wp-content/plugins/imagify/assets/images/icon-arrow-choice.png
new file mode 100644
index 00000000..2d46185d
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/icon-arrow-choice.png differ
diff --git a/wp-content/plugins/imagify/assets/images/icon-arrow-choice.svg b/wp-content/plugins/imagify/assets/images/icon-arrow-choice.svg
new file mode 100644
index 00000000..1c05e5d9
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-arrow-choice.svg
@@ -0,0 +1,12 @@
+
+
+
+ Shape
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-doc-image.svg b/wp-content/plugins/imagify/assets/images/icon-doc-image.svg
new file mode 100644
index 00000000..4b9889ea
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-doc-image.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-external.svg b/wp-content/plugins/imagify/assets/images/icon-external.svg
new file mode 100644
index 00000000..92fe1fb4
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-external.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-level.svg b/wp-content/plugins/imagify/assets/images/icon-level.svg
new file mode 100644
index 00000000..c0ffd77b
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-level.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-load.svg b/wp-content/plugins/imagify/assets/images/icon-load.svg
new file mode 100644
index 00000000..a4f60169
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-load.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-lock.png b/wp-content/plugins/imagify/assets/images/icon-lock.png
new file mode 100644
index 00000000..07d38772
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/icon-lock.png differ
diff --git a/wp-content/plugins/imagify/assets/images/icon-lock.svg b/wp-content/plugins/imagify/assets/images/icon-lock.svg
new file mode 100644
index 00000000..e935c470
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-lock.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-pack.png b/wp-content/plugins/imagify/assets/images/icon-pack.png
new file mode 100644
index 00000000..34ca691c
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/icon-pack.png differ
diff --git a/wp-content/plugins/imagify/assets/images/icon-pack.svg b/wp-content/plugins/imagify/assets/images/icon-pack.svg
new file mode 100644
index 00000000..2e6f244b
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-pack.svg
@@ -0,0 +1,16 @@
+
+
+
+ Shape
+ Created with Sketch.
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/icon-time.svg b/wp-content/plugins/imagify/assets/images/icon-time.svg
new file mode 100644
index 00000000..7487abca
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/icon-time.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/imagify-logo.png b/wp-content/plugins/imagify/assets/images/imagify-logo.png
new file mode 100644
index 00000000..94342a85
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/imagify-logo.png differ
diff --git a/wp-content/plugins/imagify/assets/images/imagify-menu-bar-de.jpg b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-de.jpg
new file mode 100644
index 00000000..1611912e
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-de.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/imagify-menu-bar-en.jpg b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-en.jpg
new file mode 100644
index 00000000..d69dbe50
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-en.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/imagify-menu-bar-es.jpg b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-es.jpg
new file mode 100644
index 00000000..de9240a2
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-es.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/imagify-menu-bar-fr.jpg b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-fr.jpg
new file mode 100644
index 00000000..dceb4460
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-fr.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/imagify-menu-bar-it.jpg b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-it.jpg
new file mode 100644
index 00000000..a694eaba
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/imagify-menu-bar-it.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/key.svg b/wp-content/plugins/imagify/assets/images/key.svg
new file mode 100644
index 00000000..cf996b31
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/key.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/lazyload.png b/wp-content/plugins/imagify/assets/images/lazyload.png
new file mode 100644
index 00000000..b077c391
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/lazyload.png differ
diff --git a/wp-content/plugins/imagify/assets/images/loader-balls.svg b/wp-content/plugins/imagify/assets/images/loader-balls.svg
new file mode 100644
index 00000000..49d74496
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/loader-balls.svg
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/logo-wprocket.png b/wp-content/plugins/imagify/assets/images/logo-wprocket.png
new file mode 100644
index 00000000..0a4d3379
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/logo-wprocket.png differ
diff --git a/wp-content/plugins/imagify/assets/images/logo-wprocket.svg b/wp-content/plugins/imagify/assets/images/logo-wprocket.svg
new file mode 100644
index 00000000..ee3e676f
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/logo-wprocket.svg
@@ -0,0 +1,47 @@
+
+
+
+ Icon / WP Rocket / Light
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/logo-wprocket@2x.png b/wp-content/plugins/imagify/assets/images/logo-wprocket@2x.png
new file mode 100644
index 00000000..72faeb3b
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/logo-wprocket@2x.png differ
diff --git a/wp-content/plugins/imagify/assets/images/mail.svg b/wp-content/plugins/imagify/assets/images/mail.svg
new file mode 100644
index 00000000..c211861d
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/mail.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/mushrooms-aggressive.jpg b/wp-content/plugins/imagify/assets/images/mushrooms-aggressive.jpg
new file mode 100644
index 00000000..9cfcdd38
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/mushrooms-aggressive.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/mushrooms-normal.jpg b/wp-content/plugins/imagify/assets/images/mushrooms-normal.jpg
new file mode 100644
index 00000000..fa7cfe20
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/mushrooms-normal.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/mushrooms-original.jpg b/wp-content/plugins/imagify/assets/images/mushrooms-original.jpg
new file mode 100644
index 00000000..1451bf08
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/mushrooms-original.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/mushrooms-ultra.jpg b/wp-content/plugins/imagify/assets/images/mushrooms-ultra.jpg
new file mode 100644
index 00000000..d8271af3
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/mushrooms-ultra.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/phone.svg b/wp-content/plugins/imagify/assets/images/phone.svg
new file mode 100644
index 00000000..35bf820d
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/phone.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/pic-ericwaltr.jpg b/wp-content/plugins/imagify/assets/images/pic-ericwaltr.jpg
new file mode 100644
index 00000000..1ca16831
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/pic-ericwaltr.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/pic-srhdesign.jpg b/wp-content/plugins/imagify/assets/images/pic-srhdesign.jpg
new file mode 100644
index 00000000..65e822df
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/pic-srhdesign.jpg differ
diff --git a/wp-content/plugins/imagify/assets/images/popin-loader.svg b/wp-content/plugins/imagify/assets/images/popin-loader.svg
new file mode 100644
index 00000000..2fa58146
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/popin-loader.svg
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/assets/images/spinner.gif b/wp-content/plugins/imagify/assets/images/spinner.gif
new file mode 100644
index 00000000..974ce638
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/spinner.gif differ
diff --git a/wp-content/plugins/imagify/assets/images/stormy.svg b/wp-content/plugins/imagify/assets/images/stormy.svg
new file mode 100644
index 00000000..7a3f3dfe
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/stormy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/sun.svg b/wp-content/plugins/imagify/assets/images/sun.svg
new file mode 100644
index 00000000..20e3af48
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/sun.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/twitter_c.svg b/wp-content/plugins/imagify/assets/images/twitter_c.svg
new file mode 100644
index 00000000..4274a296
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/twitter_c.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/images/upload-image.png b/wp-content/plugins/imagify/assets/images/upload-image.png
new file mode 100644
index 00000000..a68ac9fa
Binary files /dev/null and b/wp-content/plugins/imagify/assets/images/upload-image.png differ
diff --git a/wp-content/plugins/imagify/assets/images/user.svg b/wp-content/plugins/imagify/assets/images/user.svg
new file mode 100644
index 00000000..f3b8d057
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/images/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/admin-bar.js b/wp-content/plugins/imagify/assets/js/admin-bar.js
new file mode 100644
index 00000000..6b5777c6
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/admin-bar.js
@@ -0,0 +1,37 @@
+// Admin bar =======================================================================================
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ var busy = false;
+
+ $( d ).on( 'mouseenter', '#wp-admin-bar-imagify', function() {
+ var $adminBarProfile, url;
+
+ if ( true === busy ) {
+ return;
+ }
+
+ busy = true;
+
+ $adminBarProfile = $( '#wp-admin-bar-imagify-profile-content' );
+
+ if ( ! $adminBarProfile.is( ':empty' ) ) {
+ return;
+ }
+
+ if ( w.ajaxurl ) {
+ url = w.ajaxurl;
+ } else {
+ url = w.imagifyAdminBar.ajaxurl;
+ }
+
+ url += url.indexOf( '?' ) > 0 ? '&' : '?';
+
+ $.get( url + 'action=imagify_get_admin_bar_profile&imagifygetadminbarprofilenonce=' + $( '#imagifygetadminbarprofilenonce' ).val() )
+ .done( function( response ) {
+ $adminBarProfile.html( response.data );
+ $( '#wp-admin-bar-imagify-profile-loading' ).remove();
+ busy = false;
+ } );
+ } );
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/admin-bar.min.js b/wp-content/plugins/imagify/assets/js/admin-bar.min.js
new file mode 100644
index 00000000..1e605f63
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/admin-bar.min.js
@@ -0,0 +1 @@
+!function(a,b,c,d){var e=!1;a(b).on("mouseenter","#wp-admin-bar-imagify",function(){var b,d;!0!==e&&(e=!0,b=a("#wp-admin-bar-imagify-profile-content"),b.is(":empty")&&(d=c.ajaxurl?c.ajaxurl:c.imagifyAdminBar.ajaxurl,d+=d.indexOf("?")>0?"&":"?",a.get(d+"action=imagify_get_admin_bar_profile&imagifygetadminbarprofilenonce="+a("#imagifygetadminbarprofilenonce").val()).done(function(c){b.html(c.data),a("#wp-admin-bar-imagify-profile-loading").remove(),e=!1})))})}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/admin.js b/wp-content/plugins/imagify/assets/js/admin.js
new file mode 100644
index 00000000..984ec1a4
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/admin.js
@@ -0,0 +1,103 @@
+window.imagify = window.imagify || {};
+
+jQuery.extend( window.imagify, {
+ concat: ajaxurl.indexOf( '?' ) > 0 ? '&' : '?',
+ log: function( content ) {
+ if ( undefined !== console ) {
+ console.log( content ); // eslint-disable-line no-console
+ }
+ },
+ info: function( content ) {
+ if ( undefined !== console ) {
+ console.info( content ); // eslint-disable-line no-console
+ }
+ },
+ openModal: function( $link ) {
+ var target = $link.data( 'target' ) || $link.attr( 'href' );
+
+ jQuery( target ).css( 'display', 'flex' ).hide().fadeIn( 400 ).attr( {
+ 'aria-hidden': 'false',
+ 'tabindex': '0'
+ } ).focus().removeAttr( 'tabindex' ).addClass( 'modal-is-open' );
+
+ jQuery( 'body' ).addClass( 'imagify-modal-is-open' );
+ },
+ template: function( id ) {
+ if ( undefined === _ ) {
+ // No need to load underscore everywhere if we don't use it.
+ return '';
+ }
+
+ return _.memoize( function( data ) {
+ var compiled,
+ options = {
+ evaluate: /<#([\s\S]+?)#>/g,
+ interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
+ escape: /\{\{([^}]+?)\}\}(?!\})/g,
+ variable: 'data'
+ };
+
+ return function() {
+ compiled = compiled || _.template( jQuery( '#tmpl-' + id ).html(), null, options );
+ data = data || {};
+ return compiled( data );
+ };
+ } );
+ },
+ humanSize: function( bytes ) {
+ var sizes = ['B', 'kB', 'MB'],
+ i;
+
+ if ( 0 === bytes ) {
+ return '0\xA0kB';
+ }
+
+ i = parseInt( Math.floor( Math.log( bytes ) / Math.log( 1024 ) ), 10 );
+
+ return ( bytes / Math.pow( 1024, i ) ).toFixed( 2 ) + '\xA0' + sizes[ i ];
+ }
+} );
+
+
+// Imagify light modal =============================================================================
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ // Accessibility.
+ $( '.imagify-modal' ).attr( 'aria-hidden', 'true' );
+
+ $( d )
+ // On click on modal trigger, open modal.
+ .on( 'click.imagify', '.imagify-modal-trigger', function( e ) {
+ e.preventDefault();
+ w.imagify.openModal( $( this ) );
+ } )
+ // On click on close button, close modal.
+ .on( 'click.imagify', '.imagify-modal .close-btn', function() {
+ var $modal = $( this ).closest( '.imagify-modal' );
+
+ $modal.fadeOut( 400 ).attr( 'aria-hidden', 'true' ).removeClass( 'modal-is-open' ).trigger( 'modalClosed.imagify' );
+
+ $( 'body' ).removeClass( 'imagify-modal-is-open' );
+ } )
+ // On close button blur, improve accessibility.
+ .on( 'blur.imagify', '.imagify-modal .close-btn', function() {
+ var $modal = $( this ).closest( '.imagify-modal' );
+
+ if ( $modal.attr( 'aria-hidden' ) === 'false' ) {
+ $modal.attr( 'tabindex', '0' ).focus().removeAttr( 'tabindex' );
+ }
+ } )
+ // On click on dropped layer of modal, close modal.
+ .on( 'click.imagify', '.imagify-modal', function( e ) {
+ $( e.target ).filter( '.modal-is-open' ).find( '.close-btn' ).trigger( 'click.imagify' );
+ } )
+ // `Esc` key binding, close modal.
+ .on( 'keydown.imagify', function( e ) {
+ if ( 27 === e.keyCode && $( '.imagify-modal.modal-is-open' ).length > 0 ) {
+ e.preventDefault();
+ // Trigger the event.
+ $( '.imagify-modal.modal-is-open' ).find( '.close-btn' ).trigger( 'click.imagify' );
+ }
+ } );
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/admin.min.js b/wp-content/plugins/imagify/assets/js/admin.min.js
new file mode 100644
index 00000000..29c1d352
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/admin.min.js
@@ -0,0 +1 @@
+window.imagify=window.imagify||{},jQuery.extend(window.imagify,{concat:ajaxurl.indexOf("?")>0?"&":"?",log:function(a){void 0!==console&&console.log(a)},info:function(a){void 0!==console&&console.info(a)},openModal:function(a){var b=a.data("target")||a.attr("href");jQuery(b).css("display","flex").hide().fadeIn(400).attr({"aria-hidden":"false",tabindex:"0"}).focus().removeAttr("tabindex").addClass("modal-is-open"),jQuery("body").addClass("imagify-modal-is-open")},template:function(a){return void 0===_?"":_.memoize(function(b){var c,d={evaluate:/<#([\s\S]+?)#>/g,interpolate:/\{\{\{([\s\S]+?)\}\}\}/g,escape:/\{\{([^}]+?)\}\}(?!\})/g,variable:"data"};return function(){return c=c||_.template(jQuery("#tmpl-"+a).html(),null,d),b=b||{},c(b)}})},humanSize:function(a){var b,c=["B","kB","MB"];return 0===a?"0Â kB":(b=parseInt(Math.floor(Math.log(a)/Math.log(1024)),10),(a/Math.pow(1024,b)).toFixed(2)+"Â "+c[b])}}),function(a,b,c,d){a(".imagify-modal").attr("aria-hidden","true"),a(b).on("click.imagify",".imagify-modal-trigger",function(b){b.preventDefault(),c.imagify.openModal(a(this))}).on("click.imagify",".imagify-modal .close-btn",function(){a(this).closest(".imagify-modal").fadeOut(400).attr("aria-hidden","true").removeClass("modal-is-open").trigger("modalClosed.imagify"),a("body").removeClass("imagify-modal-is-open")}).on("blur.imagify",".imagify-modal .close-btn",function(){var b=a(this).closest(".imagify-modal");"false"===b.attr("aria-hidden")&&b.attr("tabindex","0").focus().removeAttr("tabindex")}).on("click.imagify",".imagify-modal",function(b){a(b.target).filter(".modal-is-open").find(".close-btn").trigger("click.imagify")}).on("keydown.imagify",function(b){27===b.keyCode&&a(".imagify-modal.modal-is-open").length>0&&(b.preventDefault(),a(".imagify-modal.modal-is-open").find(".close-btn").trigger("click.imagify"))})}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/beat.js b/wp-content/plugins/imagify/assets/js/beat.js
new file mode 100644
index 00000000..e4a29aff
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/beat.js
@@ -0,0 +1,872 @@
+/**
+ * Imagify beat API
+ *
+ * This is a modified version of WordPressâ Heartbeat (WP 5.2.1).
+ * The main difference is that it allows to prevent suspension entirely.
+ * It uses the var imagifybeatSettings on init.
+ *
+ * Custom jQuery events:
+ * - imagifybeat-send
+ * - imagifybeat-tick
+ * - imagifybeat-error
+ * - imagifybeat-connection-lost
+ * - imagifybeat-connection-restored
+ * - imagifybeat-nonces-expired
+ *
+ * @since 1.9.3
+ */
+
+window.imagify = window.imagify || {};
+
+/* eslint-disable no-use-before-define */
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ /**
+ * Constructs the Imagifybeat API.
+ *
+ * @since 1.9.3
+ * @constructor
+ *
+ * @return {Imagifybeat} An instance of the Imagifybeat class.
+ */
+ var Imagifybeat = function() {
+ var $document = $( d ),
+ settings = {
+ // Suspend/resume.
+ suspend: false,
+
+ // Whether suspending is enabled.
+ suspendEnabled: true,
+
+ // Current screen id, defaults to the JS global 'pagenow' when present
+ // (in the admin) or 'front'.
+ screenId: '',
+
+ // XHR request URL, defaults to the JS global 'ajaxurl' when present.
+ url: '',
+
+ // Timestamp, start of the last connection request.
+ lastTick: 0,
+
+ // Container for the enqueued items.
+ queue: {},
+
+ // Connect interval (in seconds).
+ mainInterval: 60,
+
+ // Used when the interval is set to 5 sec. temporarily.
+ tempInterval: 0,
+
+ // Used when the interval is reset.
+ originalInterval: 0,
+
+ // Used to limit the number of AJAX requests.
+ minimalInterval: 0,
+
+ // Used together with tempInterval.
+ countdown: 0,
+
+ // Whether a connection is currently in progress.
+ connecting: false,
+
+ // Whether a connection error occurred.
+ connectionError: false,
+
+ // Used to track non-critical errors.
+ errorcount: 0,
+
+ // Whether at least one connection has been completed successfully.
+ hasConnected: false,
+
+ // Whether the current browser w is in focus and the user is active.
+ hasFocus: true,
+
+ // Timestamp, last time the user was active. Checked every 30 sec.
+ userActivity: 0,
+
+ // Flag whether events tracking user activity were set.
+ userActivityEvents: false,
+
+ // Timer that keeps track of how long a user has focus.
+ checkFocusTimer: 0,
+
+ // Timer that keeps track of how long needs to be waited before connecting to
+ // the server again.
+ beatTimer: 0
+ };
+
+ /**
+ * Sets local variables and events, then starts the beat.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function initialize() {
+ var options, hidden, visibilityState, visibilitychange;
+
+ if ( typeof w.pagenow === 'string' ) {
+ settings.screenId = w.pagenow;
+ }
+
+ if ( typeof w.ajaxurl === 'string' ) {
+ settings.url = w.ajaxurl;
+ }
+
+ // Pull in options passed from PHP.
+ if ( typeof w.imagifybeatSettings === 'object' ) {
+ options = w.imagifybeatSettings;
+
+ // The XHR URL can be passed as option when w.ajaxurl is not set.
+ if ( ! settings.url && options.ajaxurl ) {
+ settings.url = options.ajaxurl;
+ }
+
+ /*
+ * The interval can be from 15 to 120 sec. and can be set temporarily to 5 sec.
+ * It can be set in the initial options or changed later through JS and/or
+ * through PHP.
+ */
+ if ( options.interval ) {
+ settings.mainInterval = options.interval;
+
+ if ( settings.mainInterval < 15 ) {
+ settings.mainInterval = 15;
+ } else if ( settings.mainInterval > 120 ) {
+ settings.mainInterval = 120;
+ }
+ }
+
+ /*
+ * Used to limit the number of AJAX requests. Overrides all other intervals if
+ * they are shorter. Needed for some hosts that cannot handle frequent requests
+ * and the user may exceed the allocated server CPU time, etc. The minimal
+ * interval can be up to 600 sec. however setting it to longer than 120 sec.
+ * will limit or disable some of the functionality (like post locks). Once set
+ * at initialization, minimalInterval cannot be changed/overridden.
+ */
+ if ( options.minimalInterval ) {
+ options.minimalInterval = parseInt( options.minimalInterval, 10 );
+ settings.minimalInterval = options.minimalInterval > 0 && options.minimalInterval <= 600 ? options.minimalInterval * 1000 : 0;
+ }
+
+ if ( settings.minimalInterval && settings.mainInterval < settings.minimalInterval ) {
+ settings.mainInterval = settings.minimalInterval;
+ }
+
+ // 'screenId' can be added from settings on the front end where the JS global
+ // 'pagenow' is not set.
+ if ( ! settings.screenId ) {
+ settings.screenId = options.screenId || 'front';
+ }
+
+ if ( 'disable' === options.suspension ) {
+ disableSuspend();
+ }
+ }
+
+ // Convert to milliseconds.
+ settings.mainInterval = settings.mainInterval * 1000;
+ settings.originalInterval = settings.mainInterval;
+
+ /*
+ * Switch the interval to 120 seconds by using the Page Visibility API.
+ * If the browser doesn't support it (Safari < 7, Android < 4.4, IE < 10), the
+ * interval will be increased to 120 seconds after 5 minutes of mouse and keyboard
+ * inactivity.
+ */
+ if ( typeof document.hidden !== 'undefined' ) {
+ hidden = 'hidden';
+ visibilitychange = 'visibilitychange';
+ visibilityState = 'visibilityState';
+ } else if ( typeof document.msHidden !== 'undefined' ) { // IE10
+ hidden = 'msHidden';
+ visibilitychange = 'msvisibilitychange';
+ visibilityState = 'msVisibilityState';
+ } else if ( typeof document.webkitHidden !== 'undefined' ) { // Android
+ hidden = 'webkitHidden';
+ visibilitychange = 'webkitvisibilitychange';
+ visibilityState = 'webkitVisibilityState';
+ }
+
+ if ( hidden ) {
+ if ( document[ hidden ] ) {
+ settings.hasFocus = false;
+ }
+
+ $document.on( visibilitychange + '.imagifybeat', function() {
+ if ( 'hidden' === document[ visibilityState ] ) {
+ blurred();
+ w.clearInterval( settings.checkFocusTimer );
+ } else {
+ focused();
+ if ( document.hasFocus ) {
+ settings.checkFocusTimer = w.setInterval( checkFocus, 10000 );
+ }
+ }
+ });
+ }
+
+ // Use document.hasFocus() if available.
+ if ( document.hasFocus ) {
+ settings.checkFocusTimer = w.setInterval( checkFocus, 10000 );
+ }
+
+ $( w ).on( 'unload.imagifybeat', function() {
+ // Don't connect anymore.
+ settings.suspend = true;
+
+ // Abort the last request if not completed.
+ if ( settings.xhr && 4 !== settings.xhr.readyState ) {
+ settings.xhr.abort();
+ }
+ } );
+
+ // Check for user activity every 30 seconds.
+ w.setInterval( checkUserActivity, 30000 );
+
+ // Start one tick after DOM ready.
+ $document.ready( function() {
+ settings.lastTick = time();
+ scheduleNextTick();
+ } );
+ }
+
+ /**
+ * Returns the current time according to the browser.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {int} Returns the current time.
+ */
+ function time() {
+ return (new Date()).getTime();
+ }
+
+ /**
+ * Checks if the iframe is from the same origin.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {bool} Returns whether or not the iframe is from the same origin.
+ */
+ function isLocalFrame( frame ) {
+ var origin, src = frame.src; // eslint-disable-line no-shadow
+
+ /*
+ * Need to compare strings as WebKit doesn't throw JS errors when iframes have different origin. It throws uncatchable exceptions.
+ */
+ if ( src && /^https?:\/\//.test( src ) ) {
+ origin = w.location.origin ? w.location.origin : w.location.protocol + '//' + w.location.host;
+
+ if ( src.indexOf( origin ) !== 0 ) {
+ return false;
+ }
+ }
+
+ try {
+ if ( frame.contentWindow.document ) {
+ return true;
+ }
+ } catch ( e ) {} // eslint-disable-line no-empty
+
+ return false;
+ }
+
+ /**
+ * Checks if the document's focus has changed.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function checkFocus() {
+ if ( settings.hasFocus && ! document.hasFocus() ) {
+ blurred();
+ } else if ( ! settings.hasFocus && document.hasFocus() ) {
+ focused();
+ }
+ }
+
+ /**
+ * Sets error state and fires an event on XHR errors or timeout.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @param {string} error The error type passed from the XHR.
+ * @param {int} httpStatus The HTTP status code passed from jqXHR (200, 404, 500, etc.).
+ * @return {void}
+ */
+ function setErrorState( error, httpStatus ) {
+ var trigger;
+
+ if ( error ) {
+ switch ( error ) {
+ case 'abort':
+ // Do nothing.
+ break;
+ case 'timeout':
+ // No response for 30 sec.
+ trigger = true;
+ break;
+ case 'error':
+ if ( 503 === httpStatus && settings.hasConnected ) {
+ trigger = true;
+ break;
+ }
+ /* falls through */
+ case 'parsererror':
+ case 'empty':
+ case 'unknown':
+ settings.errorcount++;
+
+ if ( settings.errorcount > 2 && settings.hasConnected ) {
+ trigger = true;
+ }
+
+ break;
+ }
+
+ if ( trigger && ! hasConnectionError() ) {
+ settings.connectionError = true;
+ $document.trigger( 'imagifybeat-connection-lost', [ error, httpStatus ] );
+
+ if ( w.wp.hooks ) {
+ w.wp.hooks.doAction( 'imagifybeat.connection-lost', error, httpStatus );
+ }
+ }
+ }
+ }
+
+ /**
+ * Clears the error state and fires an event if there is a connection error.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function clearErrorState() {
+ // Has connected successfully.
+ settings.hasConnected = true;
+
+ if ( hasConnectionError() ) {
+ settings.errorcount = 0;
+ settings.connectionError = false;
+ $document.trigger( 'imagifybeat-connection-restored' );
+
+ if ( w.wp.hooks ) {
+ w.wp.hooks.doAction( 'imagifybeat.connection-restored' );
+ }
+ }
+ }
+
+ /**
+ * Gathers the data and connects to the server.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function connect() {
+ var ajaxData, imagifybeatData;
+
+ // If the connection to the server is slower than the interval,
+ // imagifybeat connects as soon as the previous connection's response is received.
+ if ( settings.connecting || settings.suspend ) {
+ return;
+ }
+
+ settings.lastTick = time();
+
+ imagifybeatData = $.extend( {}, settings.queue );
+ // Clear the data queue. Anything added after this point will be sent on the next tick.
+ settings.queue = {};
+
+ $document.trigger( 'imagifybeat-send', [ imagifybeatData ] );
+
+ if ( w.wp.hooks ) {
+ w.wp.hooks.doAction( 'imagifybeat.send', imagifybeatData );
+ }
+
+ ajaxData = {
+ data: imagifybeatData,
+ interval: settings.tempInterval ? settings.tempInterval / 1000 : settings.mainInterval / 1000,
+ _nonce: typeof w.imagifybeatSettings === 'object' ? w.imagifybeatSettings.nonce : '',
+ action: 'imagifybeat',
+ screen_id: settings.screenId,
+ has_focus: settings.hasFocus
+ };
+
+ if ( 'customize' === settings.screenId ) {
+ ajaxData.wp_customize = 'on';
+ }
+
+ settings.connecting = true;
+ settings.xhr = $.ajax( {
+ url: settings.url,
+ type: 'post',
+ timeout: 60000, // Throw an error if not completed after 60 sec.
+ data: ajaxData,
+ dataType: 'json'
+ } ).always( function() {
+ settings.connecting = false;
+ scheduleNextTick();
+ } ).done( function( response, textStatus, jqXHR ) {
+ var newInterval;
+
+ if ( ! response ) {
+ setErrorState( 'empty' );
+ return;
+ }
+
+ clearErrorState();
+
+ if ( response.nonces_expired ) {
+ $document.trigger( 'imagifybeat-nonces-expired' );
+
+ if ( w.wp.hooks ) {
+ w.wp.hooks.doAction( 'imagifybeat.nonces-expired' );
+ }
+ }
+
+ // Change the interval from PHP
+ if ( response.imagifybeat_interval ) {
+ newInterval = response.imagifybeat_interval;
+ delete response.imagifybeat_interval;
+ }
+
+ // Update the imagifybeat nonce if set.
+ if ( response.imagifybeat_nonce && typeof w.imagifybeatSettings === 'object' ) {
+ w.imagifybeatSettings.nonce = response.imagifybeat_nonce;
+ delete response.imagifybeat_nonce;
+ }
+
+ $document.trigger( 'imagifybeat-tick', [ response, textStatus, jqXHR ] );
+
+ if ( w.wp.hooks ) {
+ w.wp.hooks.doAction( 'imagifybeat.tick', response, textStatus, jqXHR );
+ }
+
+ // Do this last. Can trigger the next XHR if connection time > 5 sec. and newInterval == 'fast'.
+ if ( newInterval ) {
+ interval( newInterval );
+ }
+ } ).fail( function( jqXHR, textStatus, error ) {
+ setErrorState( textStatus || 'unknown', jqXHR.status );
+ $document.trigger( 'imagifybeat-error', [ jqXHR, textStatus, error ] );
+
+ if ( w.wp.hooks ) {
+ w.wp.hooks.doAction( 'imagifybeat.error', jqXHR, textStatus, error );
+ }
+ } );
+ }
+
+ /**
+ * Schedules the next connection.
+ *
+ * Fires immediately if the connection time is longer than the interval.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function scheduleNextTick() {
+ var delta = time() - settings.lastTick,
+ interv = settings.mainInterval;
+
+ if ( settings.suspend ) {
+ return;
+ }
+
+ if ( ! settings.hasFocus && settings.suspendEnabled ) {
+ // When no user activity or the window lost focus, increase polling interval to 120 seconds, but only if suspend is enabled.
+ interv = 120000; // 120 sec.
+ } else if ( settings.countdown > 0 && settings.tempInterval ) {
+ interv = settings.tempInterval;
+ settings.countdown--;
+
+ if ( settings.countdown < 1 ) {
+ settings.tempInterval = 0;
+ }
+ }
+
+ if ( settings.minimalInterval && interv < settings.minimalInterval ) {
+ interv = settings.minimalInterval;
+ }
+
+ w.clearTimeout( settings.beatTimer );
+
+ if ( delta < interv ) {
+ settings.beatTimer = w.setTimeout(
+ function() {
+ connect();
+ },
+ interv - delta
+ );
+ } else {
+ connect();
+ }
+ }
+
+ /**
+ * Sets the internal state when the browser w becomes hidden or loses focus.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function blurred() {
+ settings.hasFocus = false;
+ }
+
+ /**
+ * Sets the internal state when the browser w becomes visible or is in focus.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function focused() {
+ settings.userActivity = time();
+
+ // Resume if suspended
+ settings.suspend = false;
+
+ if ( ! settings.hasFocus ) {
+ settings.hasFocus = true;
+ scheduleNextTick();
+ }
+ }
+
+ /**
+ * Runs when the user becomes active after a period of inactivity.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function userIsActive() {
+ settings.userActivityEvents = false;
+ $document.off( '.imagifybeat-active' );
+
+ $( 'iframe' ).each( function( i, frame ) {
+ if ( isLocalFrame( frame ) ) {
+ $( frame.contentWindow ).off( '.imagifybeat-active' );
+ }
+ } );
+
+ focused();
+ }
+
+ /**
+ * Checks for user activity.
+ *
+ * Runs every 30 sec. Sets 'hasFocus = true' if user is active and the w is
+ * in the background. Sets 'hasFocus = false' if the user has been inactive
+ * (no mouse or keyboard activity) for 5 min. even when the w has focus.
+ *
+ * @since 1.9.3
+ * @access private
+ *
+ * @return {void}
+ */
+ function checkUserActivity() {
+ var lastActive = settings.userActivity ? time() - settings.userActivity : 0;
+
+ // Set hasFocus to false when no mouse or keyboard activity for 5 min.
+ if ( lastActive > 300000 && settings.hasFocus ) {
+ blurred();
+ }
+
+ // Suspend after 10 min. of inactivity.
+ if ( settings.suspendEnabled && lastActive > 600000 ) {
+ settings.suspend = true;
+ }
+
+ if ( ! settings.userActivityEvents ) {
+ $document.on( 'mouseover.imagifybeat-active keyup.imagifybeat-active touchend.imagifybeat-active', function() {
+ userIsActive();
+ } );
+
+ $( 'iframe' ).each( function( i, frame ) {
+ if ( isLocalFrame( frame ) ) {
+ $( frame.contentWindow ).on( 'mouseover.imagifybeat-active keyup.imagifybeat-active touchend.imagifybeat-active', function() {
+ userIsActive();
+ } );
+ }
+ } );
+
+ settings.userActivityEvents = true;
+ }
+ }
+
+ // Public methods.
+
+ /**
+ * Checks whether the w (or any local iframe in it) has focus, or the user
+ * is active.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @return {bool} True if the w or the user is active.
+ */
+ function hasFocus() {
+ return settings.hasFocus;
+ }
+
+ /**
+ * Checks whether there is a connection error.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @return {bool} True if a connection error was found.
+ */
+ function hasConnectionError() {
+ return settings.connectionError;
+ }
+
+ /**
+ * Connects as soon as possible regardless of 'hasFocus' state.
+ *
+ * Will not open two concurrent connections. If a connection is in progress,
+ * will connect again immediately after the current connection completes.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @return {void}
+ */
+ function connectNow() {
+ settings.lastTick = 0;
+ scheduleNextTick();
+ }
+
+ /**
+ * Disables suspending.
+ *
+ * Should be used only when Imagifybeat is performing critical tasks like
+ * autosave, post-locking, etc. Using this on many screens may overload the
+ * user's hosting account if several browser ws/tabs are left open for a
+ * long time.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @return {void}
+ */
+ function disableSuspend() {
+ settings.suspendEnabled = false;
+ }
+
+ /**
+ * Enables suspending.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @return {void}
+ */
+ function enableSuspend() {
+ settings.suspendEnabled = true;
+ }
+
+ /**
+ * Gets/Sets the interval.
+ *
+ * When setting to 'fast' or 5, the interval is 5 seconds for the next 30 ticks
+ * (for 2 minutes and 30 seconds) by default. In this case the number of 'ticks'
+ * can be passed as second argument. If the window doesn't have focus, the
+ * interval slows down to 2 min.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @param {string|int} speed Interval: 'fast' or 5, 15, 30, 60, 120. Fast equals 5.
+ * @param {string} ticks Tells how many ticks before the interval reverts back. Used with speed = 'fast' or 5.
+ * @return {int} Current interval in seconds.
+ */
+ function interval( speed, ticks ) {
+ var newInterval,
+ oldInterval = settings.tempInterval ? settings.tempInterval : settings.mainInterval;
+
+ if ( speed ) {
+ switch ( speed ) {
+ case 'fast':
+ case 5:
+ newInterval = 5000;
+ break;
+ case 15:
+ newInterval = 15000;
+ break;
+ case 30:
+ newInterval = 30000;
+ break;
+ case 60:
+ newInterval = 60000;
+ break;
+ case 120:
+ newInterval = 120000;
+ break;
+ case 'long-polling':
+ // Allow long polling, (experimental)
+ settings.mainInterval = 0;
+ return 0;
+ default:
+ newInterval = settings.originalInterval;
+ }
+
+ if ( settings.minimalInterval && newInterval < settings.minimalInterval ) {
+ newInterval = settings.minimalInterval;
+ }
+
+ if ( 5000 === newInterval ) {
+ ticks = parseInt( ticks, 10 ) || 30;
+ ticks = ticks < 1 || ticks > 30 ? 30 : ticks;
+
+ settings.countdown = ticks;
+ settings.tempInterval = newInterval;
+ } else {
+ settings.countdown = 0;
+ settings.tempInterval = 0;
+ settings.mainInterval = newInterval;
+ }
+
+ // Change the next connection time if new interval has been set.
+ // Will connect immediately if the time since the last connection
+ // is greater than the new interval.
+ if ( newInterval !== oldInterval ) {
+ scheduleNextTick();
+ }
+ }
+
+ return settings.tempInterval ? settings.tempInterval / 1000 : settings.mainInterval / 1000;
+ }
+
+ /**
+ * Resets the interval.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @return {int} Current interval in seconds.
+ */
+ function resetInterval() {
+ return interval( settings.originalInterval );
+ }
+
+ /**
+ * Enqueues data to send with the next XHR.
+ *
+ * As the data is send asynchronously, this function doesn't return the XHR
+ * response. To see the response, use the custom jQuery event 'imagifybeat-tick'
+ * on the document, example:
+ * $(document).on( 'imagifybeat-tick.myname', function( event, data, textStatus, jqXHR ) {
+ * // code
+ * });
+ * If the same 'handle' is used more than once, the data is not overwritten when
+ * the third argument is 'true'. Use `imagify.beat.isQueued('handle')` to see if
+ * any data is already queued for that handle.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @param {string} handle Unique handle for the data, used in PHP to receive the data.
+ * @param {mixed} data The data to send.
+ * @param {bool} noOverwrite Whether to overwrite existing data in the queue.
+ * @return {bool} True if the data was queued.
+ */
+ function enqueue( handle, data, noOverwrite ) {
+ if ( handle ) {
+ if ( noOverwrite && this.isQueued( handle ) ) {
+ return false;
+ }
+
+ settings.queue[handle] = data;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if data with a particular handle is queued.
+ *
+ * @since 1.9.3
+ *
+ * @param {string} handle The handle for the data.
+ * @return {bool} True if the data is queued with this handle.
+ */
+ function isQueued( handle ) {
+ if ( handle ) {
+ return settings.queue.hasOwnProperty( handle );
+ }
+ }
+
+ /**
+ * Removes data with a particular handle from the queue.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @param {string} handle The handle for the data.
+ */
+ function dequeue( handle ) {
+ if ( handle ) {
+ delete settings.queue[handle];
+ }
+ }
+
+ /**
+ * Gets data that was enqueued with a particular handle.
+ *
+ * @since 1.9.3
+ * @memberOf imagify.beat.prototype
+ *
+ * @param {string} handle The handle for the data.
+ * @return {mixed} The data or undefined.
+ */
+ function getQueuedItem( handle ) {
+ if ( handle ) {
+ return this.isQueued( handle ) ? settings.queue[ handle ] : undefined;
+ }
+ }
+
+ initialize();
+
+ // Expose public methods.
+ return {
+ hasFocus: hasFocus,
+ connectNow: connectNow,
+ disableSuspend: disableSuspend,
+ enableSuspend: enableSuspend,
+ interval: interval,
+ resetInterval: resetInterval,
+ hasConnectionError: hasConnectionError,
+ enqueue: enqueue,
+ dequeue: dequeue,
+ isQueued: isQueued,
+ getQueuedItem: getQueuedItem
+ };
+ };
+
+ /**
+ * Contains the Imagifybeat API.
+ *
+ * @namespace imagify.beat
+ * @type {Imagifybeat}
+ */
+ w.imagify.beat = new Imagifybeat();
+
+} )( jQuery, document, window );
diff --git a/wp-content/plugins/imagify/assets/js/beat.min.js b/wp-content/plugins/imagify/assets/js/beat.min.js
new file mode 100644
index 00000000..91090ef2
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/beat.min.js
@@ -0,0 +1 @@
+window.imagify=window.imagify||{},function(a,b,c,d){var e=function(){function e(){return(new Date).getTime()}function f(a){var b,d=a.src;if(d&&/^https?:\/\//.test(d)&&(b=c.location.origin?c.location.origin:c.location.protocol+"//"+c.location.host,0!==d.indexOf(b)))return!1;try{if(a.contentWindow.document)return!0}catch(a){}return!1}function g(){B.hasFocus&&!document.hasFocus()?l():!B.hasFocus&&document.hasFocus()&&m()}function h(a,b){var d;if(a){switch(a){case"abort":break;case"timeout":d=!0;break;case"error":if(503===b&&B.hasConnected){d=!0;break}case"parsererror":case"empty":case"unknown":B.errorcount++,B.errorcount>2&&B.hasConnected&&(d=!0)}d&&!q()&&(B.connectionError=!0,A.trigger("imagifybeat-connection-lost",[a,b]),c.wp.hooks&&c.wp.hooks.doAction("imagifybeat.connection-lost",a,b))}}function i(){B.hasConnected=!0,q()&&(B.errorcount=0,B.connectionError=!1,A.trigger("imagifybeat-connection-restored"),c.wp.hooks&&c.wp.hooks.doAction("imagifybeat.connection-restored"))}function j(){var b,d;B.connecting||B.suspend||(B.lastTick=e(),d=a.extend({},B.queue),B.queue={},A.trigger("imagifybeat-send",[d]),c.wp.hooks&&c.wp.hooks.doAction("imagifybeat.send",d),b={data:d,interval:B.tempInterval?B.tempInterval/1e3:B.mainInterval/1e3,_nonce:"object"==typeof c.imagifybeatSettings?c.imagifybeatSettings.nonce:"",action:"imagifybeat",screen_id:B.screenId,has_focus:B.hasFocus},"customize"===B.screenId&&(b.wp_customize="on"),B.connecting=!0,B.xhr=a.ajax({url:B.url,type:"post",timeout:6e4,data:b,dataType:"json"}).always(function(){B.connecting=!1,k()}).done(function(a,b,d){var e;if(!a)return void h("empty");i(),a.nonces_expired&&(A.trigger("imagifybeat-nonces-expired"),c.wp.hooks&&c.wp.hooks.doAction("imagifybeat.nonces-expired")),a.imagifybeat_interval&&(e=a.imagifybeat_interval,delete a.imagifybeat_interval),a.imagifybeat_nonce&&"object"==typeof c.imagifybeatSettings&&(c.imagifybeatSettings.nonce=a.imagifybeat_nonce,delete a.imagifybeat_nonce),A.trigger("imagifybeat-tick",[a,b,d]),c.wp.hooks&&c.wp.hooks.doAction("imagifybeat.tick",a,b,d),e&&u(e)}).fail(function(a,b,d){h(b||"unknown",a.status),A.trigger("imagifybeat-error",[a,b,d]),c.wp.hooks&&c.wp.hooks.doAction("imagifybeat.error",a,b,d)}))}function k(){var a=e()-B.lastTick,b=B.mainInterval;B.suspend||(!B.hasFocus&&B.suspendEnabled?b=12e4:B.countdown>0&&B.tempInterval&&(b=B.tempInterval,--B.countdown<1&&(B.tempInterval=0)),B.minimalInterval&&b3e5&&B.hasFocus&&l(),B.suspendEnabled&&b>6e5&&(B.suspend=!0),B.userActivityEvents||(A.on("mouseover.imagifybeat-active keyup.imagifybeat-active touchend.imagifybeat-active",function(){n()}),a("iframe").each(function(b,c){f(c)&&a(c.contentWindow).on("mouseover.imagifybeat-active keyup.imagifybeat-active touchend.imagifybeat-active",function(){n()})}),B.userActivityEvents=!0)}function p(){return B.hasFocus}function q(){return B.connectionError}function r(){B.lastTick=0,k()}function s(){B.suspendEnabled=!1}function t(){B.suspendEnabled=!0}function u(a,b){var c,d=B.tempInterval?B.tempInterval:B.mainInterval;if(a){switch(a){case"fast":case 5:c=5e3;break;case 15:c=15e3;break;case 30:c=3e4;break;case 60:c=6e4;break;case 120:c=12e4;break;case"long-polling":return B.mainInterval=0,0;default:c=B.originalInterval}B.minimalInterval&&c30?30:b,B.countdown=b,B.tempInterval=c):(B.countdown=0,B.tempInterval=0,B.mainInterval=c),c!==d&&k()}return B.tempInterval?B.tempInterval/1e3:B.mainInterval/1e3}function v(){return u(B.originalInterval)}function w(a,b,c){return!!a&&((!c||!this.isQueued(a))&&(B.queue[a]=b,!0))}function x(a){if(a)return B.queue.hasOwnProperty(a)}function y(a){a&&delete B.queue[a]}function z(a){if(a)return this.isQueued(a)?B.queue[a]:d}var A=a(b),B={suspend:!1,suspendEnabled:!0,screenId:"",url:"",lastTick:0,queue:{},mainInterval:60,tempInterval:0,originalInterval:0,minimalInterval:0,countdown:0,connecting:!1,connectionError:!1,errorcount:0,hasConnected:!1,hasFocus:!0,userActivity:0,userActivityEvents:!1,checkFocusTimer:0,beatTimer:0};return function(){var b,d,f,h;"string"==typeof c.pagenow&&(B.screenId=c.pagenow),"string"==typeof c.ajaxurl&&(B.url=c.ajaxurl),"object"==typeof c.imagifybeatSettings&&(b=c.imagifybeatSettings,!B.url&&b.ajaxurl&&(B.url=b.ajaxurl),b.interval&&(B.mainInterval=b.interval,B.mainInterval<15?B.mainInterval=15:B.mainInterval>120&&(B.mainInterval=120)),b.minimalInterval&&(b.minimalInterval=parseInt(b.minimalInterval,10),B.minimalInterval=b.minimalInterval>0&&b.minimalInterval<=600?1e3*b.minimalInterval:0),B.minimalInterval&&B.mainInterval 0 ) {
+ this.hide( duration, function() {
+ $( this ).addClass( 'hidden' ).css( 'display', '' );
+
+ if ( undefined !== callback ) {
+ callback();
+ }
+ } );
+ } else {
+ this.addClass( 'hidden' );
+
+ if ( undefined !== callback ) {
+ callback();
+ }
+ }
+
+ return this.attr( 'aria-hidden', 'true' );
+ };
+
+ /**
+ * Show element(s).
+ *
+ * @param {int} duration A duration in ms.
+ * @param {function} callback A callback to execute before starting to display the element.
+ * @return {element} The jQuery element(s).
+ */
+ $.fn.imagifyShow = function( duration, callback ) {
+ if ( undefined !== callback ) {
+ callback();
+ }
+
+ if ( duration && duration > 0 ) {
+ this.show( duration, function() {
+ $( this ).removeClass( 'hidden' ).css( 'display', '' );
+ } );
+ } else {
+ this.removeClass( 'hidden' );
+ }
+
+ return this.attr( 'aria-hidden', 'false' );
+ };
+
+}( jQuery ));
+
+
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ w.imagify.bulk = {
+
+ // Properties ==============================================================================
+ charts: {
+ overview: {
+ canvas: false,
+ donut: false,
+ data: {
+ // Order: unoptimized, optimized, error.
+ labels: [
+ imagifyBulk.labels.overviewChartLabels.unoptimized,
+ imagifyBulk.labels.overviewChartLabels.optimized,
+ imagifyBulk.labels.overviewChartLabels.error
+ ],
+ datasets: [ {
+ data: [],
+ backgroundColor: [ '#10121A', '#46B1CE', '#C51162' ],
+ borderWidth: 0
+ } ]
+ }
+ },
+ files: {
+ donuts: {}
+ },
+ share: {
+ canvas: false,
+ donut: false
+ }
+ },
+ /**
+ * Folder types in queue.
+ * An array of objects: {
+ * @type {string} groupID The group ID, like 'library'.
+ * @type {string} context The context, like 'wp'.
+ * @type {int} level The optimization level: 0, 1, or 2.
+ * }
+ */
+ folderTypesQueue: [],
+ /**
+ * Status of each folder type. Type IDs are used as keys.
+ * Each object contains: {
+ * @type {bool} isError Tell if the status is considered as an error.
+ * @type {string} id ID of the status, like 'waiting', 'fetching', or 'optimizing'.
+ * }
+ */
+ status: {},
+ // Tell if the message displayed when retrieving the image IDs has been shown once.
+ displayedWaitMessage: false,
+ // Tell how many rows are available.
+ hasMultipleRows: true,
+ // The action to perform (like 'optimize').
+ imagifyAction: '',
+ // Set to true to stop the whole thing.
+ processIsStopped: false,
+ // List of medias being processed.
+ processingMedia: [],
+ // Global stats.
+ globalGain: 0,
+ globalOriginalSize: 0,
+ globalOptimizedSize: 0,
+ /**
+ * Folder types used in the page.
+ *
+ * @var {object} {
+ * An object of objects. The keys are like: {groupID|context}.
+ *
+ * @type {string} groupID The group ID.
+ * @type {string} context The context.
+ * @type {int} level The optimization.
+ * @type {string} optimizeURL The URL to ping to optimize a file.
+ * @type {array} mediaIDs A list of file IDs.
+ * @type {object} files File IDs as keys (prefixed by an underscore), File URLs as values.
+ * }
+ */
+ folderTypesData: {},
+ // Default thumbnail.
+ defaultThumb: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhBAMAAAClyt9cAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABtQTFRFR3BMjo6Oh4eHqqqq4uLigoKC+fn5fn5+fHx8SBBv5wAAAAF0Uk5TAEDm2GYAAABWSURBVCjPY2BgEBIEAyERBhgQUoKAIAc0EXXjEDQRRTNzBzRdBokhaGoM2CQc0NQwJJegqWFgM3VAUSNsbGwugCKiqBQUpIAiAgICoyIDKyIIB0JAEQA54jRBweNV0AAAAABJRU5ErkJggg==',
+
+ // Methods =================================================================================
+
+ /*
+ * Init.
+ */
+ init: function () {
+ var $document = $( d );
+
+ // Overview chart.
+ this.drawOverviewChart();
+
+ this.hasMultipleRows = $( '.imagify-bulk-table [name="group[]"]' ).length > 1;
+
+ // Selectors (like the level selectors).
+ $( '.imagify-selector-button' )
+ .on( 'click.imagify', this.openSelectorFromButton );
+
+ $( '.imagify-selector-list input' )
+ .on( 'change.imagify init.imagify', this.syncSelectorFromRadio )
+ .filter( ':checked' )
+ .trigger( 'init.imagify' );
+
+ $document
+ .on( 'keypress.imagify click.imagify', this.closeSelectors );
+
+ // Other buttons/UI.
+ $( '.imagify-bulk-table [name="group[]"]' )
+ .on( 'change.imagify init.imagify', this.toggleOptimizationButton )
+ .trigger( 'init.imagify' );
+
+ $( '.imagify-show-table-details' )
+ .on( 'click.imagify open.imagify close.imagify', this.toggleOptimizationDetails );
+
+ $( '#imagify-bulk-action' )
+ .on( 'click.imagify', this.maybeLaunchAllProcesses );
+
+ $( '.imagify-share-networks a' )
+ .on( 'click.imagify', this.share );
+
+ // Optimization events.
+ $( w )
+ .on( 'processQueue.imagify', this.processQueue )
+ .on( 'optimizeFiles.imagify', this.optimizeFiles )
+ .on( 'queueEmpty.imagify', this.queueEmpty );
+
+ if ( imagifyBulk.ajaxActions.getStats && $( '.imagify-bulk-table [data-group-id="library"][data-context="wp"]' ).length ) {
+ // On large WP library, don't request stats periodically, only when everything is done.
+ imagifyBulk.imagifybeatIDs.stats = false;
+ }
+
+ if ( imagifyBulk.imagifybeatIDs.stats ) {
+ // Imagifybeat for stats.
+ $document
+ .on( 'imagifybeat-send', this.addStatsImagifybeat )
+ .on( 'imagifybeat-tick', this.processStatsImagifybeat );
+ }
+
+ // Imagifybeat for optimization queue.
+ $document
+ .on( 'imagifybeat-send', this.addQueueImagifybeat )
+ .on( 'imagifybeat-tick', this.processQueueImagifybeat );
+
+ // Imagifybeat for requirements.
+ $document
+ .on( 'imagifybeat-send', this.addRequirementsImagifybeat )
+ .on( 'imagifybeat-tick', this.processRequirementsImagifybeat );
+ },
+
+ /*
+ * Get the URL used for ajax requests.
+ *
+ * @param {string} action An ajax action, or part of it.
+ * @param {object} item The current item.
+ * @return {string}
+ */
+ getAjaxUrl: function ( action, item ) {
+ var url = ajaxurl + w.imagify.concat + '_wpnonce=' + imagifyBulk.ajaxNonce + '&action=' + imagifyBulk.ajaxActions[ action ];
+
+ if ( item && item.context ) {
+ url += '&context=' + item.context;
+ }
+
+ if ( 'getMediaIds' === action || 'bulkProcess' === action ) {
+ url += '&imagify_action=' + w.imagify.bulk.imagifyAction;
+ }
+
+ return url;
+ },
+
+ /**
+ * Get folder types used in the page.
+ *
+ * @see this.folderTypesData
+ * @return {object}
+ */
+ getFolderTypes: function () {
+ if ( ! $.isEmptyObject( w.imagify.bulk.folderTypesData ) ) {
+ return w.imagify.bulk.folderTypesData;
+ }
+
+ $( '.imagify-row-folder-type' ).each( function() {
+ var $this = $( this ),
+ data = {
+ groupID: $this.data( 'group-id' ),
+ context: $this.data( 'context' )
+ },
+ key = data.groupID + '|' + data.context;
+
+ w.imagify.bulk.folderTypesData[ key ] = data;
+ } );
+
+ return w.imagify.bulk.folderTypesData;
+ },
+
+ /*
+ * Get the message displayed to the user when (s)he leaves the page.
+ *
+ * @return {string}
+ */
+ getConfirmMessage: function () {
+ return imagifyBulk.labels.processing;
+ },
+
+ /*
+ * Close the given optimization level selector.
+ *
+ * @param {object} $lists A jQuery object.
+ * @param {int} timer Timer in ms to close the selector.
+ */
+ closeLevelSelector: function ( $lists, timer ) {
+ if ( ! $lists || ! $lists.length ) {
+ return;
+ }
+
+ if ( undefined !== timer && timer > 0 ) {
+ w.setTimeout( function() {
+ w.imagify.bulk.closeLevelSelector( $lists );
+ }, timer );
+ return;
+ }
+
+ $lists.attr( 'aria-hidden', 'true' );
+ },
+
+ /*
+ * Stop everything and update the current item status as an error.
+ *
+ * @param {string} errorId An error ID.
+ * @param {object} item The current item.
+ */
+ stopProcess: function ( errorId, item ) {
+ this.processIsStopped = true;
+
+ w.imagify.bulk.status[ item.groupID ] = {
+ isError: true,
+ id: errorId
+ };
+
+ $( w ).trigger( 'queueEmpty.imagify' );
+ },
+
+ /*
+ * Tell if we have a blocking error. Can also display an error message in a swal.
+ *
+ * @param {bool} displayErrorMessage False to not display any error message.
+ * @return {bool}
+ */
+ hasBlockingError: function ( displayErrorMessage ) {
+ displayErrorMessage = undefined !== displayErrorMessage && displayErrorMessage;
+
+ if ( imagifyBulk.curlMissing ) {
+ if ( displayErrorMessage ) {
+ w.imagify.bulk.displayError( {
+ html: imagifyBulk.labels.curlMissing
+ } );
+ }
+ return true;
+ }
+
+ if ( imagifyBulk.editorMissing ) {
+ if ( displayErrorMessage ) {
+ w.imagify.bulk.displayError( {
+ html: imagifyBulk.labels.editorMissing
+ } );
+ }
+ return true;
+ }
+
+ if ( imagifyBulk.extHttpBlocked ) {
+ if ( displayErrorMessage ) {
+ w.imagify.bulk.displayError( {
+ html: imagifyBulk.labels.extHttpBlocked
+ } );
+ }
+ return true;
+ }
+
+ if ( imagifyBulk.apiDown ) {
+ if ( displayErrorMessage ) {
+ w.imagify.bulk.displayError( {
+ html: imagifyBulk.labels.apiDown
+ } );
+ }
+ return true;
+ }
+
+ if ( ! imagifyBulk.keyIsValid ) {
+ if ( displayErrorMessage ) {
+ w.imagify.bulk.displayError( {
+ title: imagifyBulk.labels.invalidAPIKeyTitle,
+ type: 'info'
+ } );
+ }
+ return true;
+ }
+
+ if ( imagifyBulk.isOverQuota ) {
+ if ( displayErrorMessage ) {
+ w.imagify.bulk.displayError( {
+ title: imagifyBulk.labels.overQuotaTitle,
+ html: $( '#tmpl-imagify-overquota-alert' ).html(),
+ type: 'info',
+ customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
+ showConfirmButton: false
+ } );
+ }
+ return true;
+ }
+
+ return false;
+ },
+
+ /*
+ * Display an error message in a modal.
+ *
+ * @param {string} title The modal title.
+ * @param {string} text The modal text.
+ * @param {object} args Other less common args.
+ */
+ displayError: function ( title, text, args ) {
+ var def = {
+ title: '',
+ html: '',
+ type: 'error',
+ customClass: '',
+ width: 620,
+ padding: 0,
+ showCloseButton: true,
+ showConfirmButton: true
+ };
+
+ if ( $.isPlainObject( title ) ) {
+ args = $.extend( {}, def, title );
+ } else {
+ args = args || {};
+ args = $.extend( {}, def, {
+ title: title || '',
+ html: text || ''
+ }, args );
+ }
+
+ args.title = args.title || imagifyBulk.labels.error;
+ args.customClass += ' imagify-sweet-alert';
+
+ swal( args ).catch( swal.noop );
+ },
+
+ /*
+ * Display an error message in a file row.
+ *
+ * @param {function} $row The row template.
+ * @param {string} text The error text.
+ * @return {element} The row jQuery element.
+ */
+ displayErrorInRow: function ( $row, text ) {
+ var $toReplace, colspan;
+
+ $row = $( $row() );
+ $toReplace = $row.find( '.imagify-cell-status ~ td' );
+ colspan = $toReplace.length;
+ text = text || '';
+
+ $toReplace.remove();
+ $row.find( '.imagify-cell-status' ).after( '' + text + ' ' );
+
+ return $row;
+ },
+
+ /*
+ * Display one of the 3 "folder" rows.
+ *
+ * @param {string} state One of the 3 states: 'resting' (it's the "normal" row), 'waiting' (waiting for other optimizations to finish), and 'working'.
+ * @param {element} $row jQuery element of the "normal" row.
+ */
+ displayFolderRow: function ( state, $row ) {
+ var $newRow, spinnerTemplate, spinnerColor, text;
+
+ if ( 'resting' === state ) {
+ $row.next( '.imagify-row-waiting, .imagify-row-working' ).remove();
+ $row.imagifyShow();
+ return;
+ }
+
+ // This part won't work to display multiple $newRow.
+ $newRow = $row.next( '.imagify-row-waiting, .imagify-row-working' );
+
+ if ( 'waiting' === state ) {
+ spinnerColor = '#d2d3d6';
+ text = imagifyBulk.labels.waitingOtimizationsText;
+ } else {
+ spinnerColor = '#40b1d0';
+ text = imagifyBulk.labels.imagesOptimizedText.replace( '%s', '0 ' );
+ }
+
+ if ( $newRow.length ) {
+ if ( ! $newRow.hasClass( 'imagify-row-' + state ) ) {
+ // Should happen when switching from 'waiting' to 'working'.
+ $newRow.attr( 'class', 'imagify-row-' + state );
+ $newRow.find( '.imagify-cell-checkbox svg' ).attr( 'fill', spinnerColor );
+ $newRow.children( '.imagify-cell-count-optimized' ).html( text );
+ }
+
+ $row.imagifyHide();
+ $newRow.imagifyShow();
+ return;
+ }
+
+ // Build the new row, based on a clone of the original one.
+ $newRow = $row.clone().attr( {
+ 'class': 'imagify-row-' + state,
+ 'aria-hidden': 'false'
+ } );
+
+ spinnerTemplate = w.imagify.template( 'imagify-spinner' );
+ $newRow.children( '.imagify-cell-checkbox' ).html( spinnerTemplate() ).find( 'svg' ).attr( 'fill', spinnerColor );
+ $newRow.children( '.imagify-cell-title' ).html( '' + $newRow.children( '.imagify-cell-title' ).text() + ' ' );
+ $newRow.children( '.imagify-cell-count-optimized' ).html( text );
+ $newRow.children( '.imagify-cell-count-errors, .imagify-cell-optimized-size, .imagify-cell-original-size, .imagify-cell-level' ).text( '' );
+
+ $row.imagifyHide().after( $newRow );
+ },
+
+ /*
+ * Display the share box.
+ */
+ displayShareBox: function () {
+ var text2share = imagifyBulk.labels.textToShare,
+ percent, gainHuman, originalSizeHuman,
+ $complete;
+
+ if ( ! this.globalGain || this.folderTypesQueue.length ) {
+ this.globalGain = 0;
+ this.globalOriginalSize = 0;
+ this.globalOptimizedSize = 0;
+ return;
+ }
+
+ percent = ( 100 - 100 * ( this.globalOptimizedSize / this.globalOriginalSize ) ).toFixed( 2 );
+ gainHuman = w.imagify.humanSize( this.globalGain, 1 );
+ originalSizeHuman = w.imagify.humanSize( this.globalOriginalSize, 1 );
+
+ text2share = text2share.replace( '%1$s', gainHuman );
+ text2share = text2share.replace( '%2$s', originalSizeHuman );
+ text2share = encodeURIComponent( text2share );
+
+ $complete = $( '.imagify-row-complete' );
+ $complete.find( '.imagify-ac-rt-total-gain' ).html( gainHuman );
+ $complete.find( '.imagify-ac-rt-total-original' ).html( originalSizeHuman );
+ $complete.find( '.imagify-ac-chart' ).attr( 'data-percent', percent );
+ $complete.find( '.imagify-sn-twitter' ).attr( 'href', imagifyBulk.labels.twitterShareURL + '&text=' + text2share );
+
+ // Chart.
+ this.drawShareChart();
+
+ $complete.addClass( 'done' ).imagifyShow();
+
+ $( 'html, body' ).animate( {
+ scrollTop: $complete.offset().top
+ }, 200 );
+
+ // Reset the stats.
+ this.globalGain = 0;
+ this.globalOriginalSize = 0;
+ this.globalOptimizedSize = 0;
+ },
+
+ /**
+ * Print optimization stats.
+ *
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ updateStats: function ( data ) {
+ var donutData;
+
+ if ( ! data || ! $.isPlainObject( data ) ) {
+ return;
+ }
+
+ if ( w.imagify.bulk.charts.overview.donut.data ) {
+ donutData = w.imagify.bulk.charts.overview.donut.data.datasets[0].data;
+
+ if ( data.unoptimized_attachments === donutData[0] && data.optimized_attachments === donutData[1] && data.errors_attachments === donutData[2] ) {
+ return;
+ }
+ }
+
+ /**
+ * User account.
+ */
+ data.unconsumed_quota = data.unconsumed_quota.toFixed( 1 ); // A mystery where a float rounded on php side is not rounded here anymore. JavaScript is fun, it always surprises you in a manner you didn't expect.
+ $( '.imagify-meteo-icon' ).html( data.quota_icon );
+ $( '.imagify-unconsumed-percent' ).html( data.unconsumed_quota + '%' );
+ $( '.imagify-unconsumed-bar' ).css( 'width', data.unconsumed_quota + '%' ).parent().attr( 'class', data.quota_class );
+
+ /**
+ * Global chart.
+ */
+ $( '#imagify-overview-chart-percent' ).html( data.optimized_attachments_percent + '% ' );
+ $( '.imagify-total-percent' ).html( data.optimized_attachments_percent + '%' );
+
+ w.imagify.bulk.drawOverviewChart( [
+ data.unoptimized_attachments,
+ data.optimized_attachments,
+ data.errors_attachments
+ ] );
+
+ /**
+ * Stats block.
+ */
+ // The total optimized images.
+ $( '#imagify-total-optimized-attachments' ).html( data.already_optimized_attachments );
+
+ // The original bar.
+ $( '#imagify-original-bar' ).find( '.imagify-barnb' ).html( data.original_human );
+
+ // The optimized bar.
+ $( '#imagify-optimized-bar' ).css( 'width', ( 100 - data.optimized_percent ) + '%' ).find( '.imagify-barnb' ).html( data.optimized_human );
+
+ // The Percent data.
+ $( '#imagify-total-optimized-attachments-pct' ).html( data.optimized_percent + '%' );
+ },
+
+ // Event callbacks =========================================================================
+
+ /*
+ * Selector (like optimization level selector): on button click, open the dropdown and focus the current radio input.
+ * The dropdown must be open or the focus event won't be triggered.
+ *
+ * @param {object} e jQuery's Event object.
+ */
+ openSelectorFromButton: function ( e ) {
+ var $list = $( '#' + $( this ).attr( 'aria-controls' ) );
+ // Stop click event from bubbling: this will allow to close the selector list if anything else id clicked.
+ e.stopPropagation();
+ // Close other lists.
+ $( '.imagify-selector-list' ).not( $list ).attr( 'aria-hidden', 'true' );
+ // Open the corresponding list and focus the radio.
+ $list.attr( 'aria-hidden', 'false' ).find( ':checked' ).trigger( 'focus.imagify' );
+ },
+
+ /*
+ * Selector: on radio change, make the row "current" and update the button text.
+ */
+ syncSelectorFromRadio: function () {
+ var $row = $( this ).closest( '.imagify-selector-choice' );
+ // Update rows attributes.
+ $row.addClass( 'imagify-selector-current-value' ).attr( 'aria-current', 'true' ).siblings( '.imagify-selector-choice' ).removeClass( 'imagify-selector-current-value' ).attr( 'aria-current', 'false' );
+ // Change the button text.
+ $row.closest( '.imagify-selector-list' ).siblings( '.imagify-selector-button' ).find( '.imagify-selector-current-value-info' ).html( $row.find( 'label' ).html() );
+ },
+
+ /*
+ * Selector: on Escape or Enter kaystroke, close the dropdown.
+ *
+ * @param {object} e jQuery's Event object.
+ */
+ closeSelectors: function ( e ) {
+ if ( 'keypress' === e.type && 27 !== e.keyCode && 13 !== e.keyCode ) {
+ return;
+ }
+ w.imagify.bulk.closeLevelSelector( $( '.imagify-selector-list[aria-hidden="false"]' ) );
+ },
+
+ /*
+ * Enable or disable the Optimization button depending on the checked checkboxes.
+ * Also, if there is only 1 checkbox in the page, don't allow it to be unchecked.
+ */
+ toggleOptimizationButton: function () {
+ // Prevent uncheck if there is only one checkbox.
+ if ( ! w.imagify.bulk.hasMultipleRows && ! this.checked ) {
+ $( this ).prop( 'checked', true );
+ return;
+ }
+
+ // Enable or disable the Optimization button.
+ if ( $( '.imagify-bulk-table [name="group[]"]:checked' ).length ) {
+ $( '#imagify-bulk-action' ).removeAttr( 'disabled' );
+ } else {
+ $( '#imagify-bulk-action' ).attr( 'disabled', 'disabled' );
+ }
+ },
+
+ /*
+ * Display/Hide optimization details.
+ *
+ * @param {object} e jQuery's Event object.
+ */
+ toggleOptimizationDetails: function ( e ) {
+ var $button = $( this ),
+ $details = $button.closest( '.imagify-bulk-table' ).find( '.imagify-bulk-table-details' ),
+ openDetails;
+
+ if ( 'open' === e.type ) {
+ openDetails = true;
+ } else if ( 'close' === e.type ) {
+ openDetails = false;
+ } else {
+ openDetails = $details.hasClass( 'hidden' );
+ }
+
+ if ( openDetails ) {
+ $button.html( $button.data( 'label-hide' ) + ' ' );
+ $details.imagifyShow();
+ } else {
+ $button.html( $button.data( 'label-show' ) + '' );
+ $details.imagifyHide();
+ }
+ },
+
+ /*
+ * Maybe display a modal, then launch all processes.
+ */
+ maybeLaunchAllProcesses: function () {
+ var $infosModal;
+
+ if ( $( this ).attr( 'disabled' ) ) {
+ return;
+ }
+
+ if ( ! $( '.imagify-bulk-table [name="group[]"]:checked' ).length ) {
+ return;
+ }
+
+ if ( w.imagify.bulk.hasBlockingError( true ) ) {
+ return;
+ }
+
+ $infosModal = $( '#tmpl-imagify-bulk-infos' );
+
+ if ( ! $infosModal.length ) {
+ w.imagify.bulk.launchAllProcesses();
+ return;
+ }
+
+ // Swal Information before loading the optimize process.
+ swal( {
+ title: imagifyBulk.labels.bulkInfoTitle,
+ html: $infosModal.html(),
+ type: '',
+ customClass: 'imagify-sweet-alert imagify-swal-has-subtitle imagify-before-bulk-infos',
+ showCancelButton: true,
+ padding: 0,
+ width: 554,
+ confirmButtonText: imagifyBulk.labels.confirmBulk,
+ cancelButtonText: imagifySwal.labels.cancelButtonText,
+ reverseButtons: true
+ } ).then( function() {
+ var $row = $( '.imagify-bulk-table [name="group[]"]:checked' ).first().closest( '.imagify-row-folder-type' );
+
+ $.get( w.imagify.bulk.getAjaxUrl( 'bulkInfoSeen', {
+ context: $row.data( 'context' )
+ } ) );
+
+ $infosModal.remove();
+
+ w.imagify.bulk.launchAllProcesses();
+ } ).catch( swal.noop );
+ },
+
+ /*
+ * Build the queue and launch all processes.
+ */
+ launchAllProcesses: function () {
+ var $w = $( w ),
+ $button = $( '#imagify-bulk-action' ),
+ skip = true;
+
+ // Disable the button.
+ $button.attr( 'disabled', 'disabled' ).find( '.dashicons' ).addClass( 'rotate' );
+
+ // Add a message to be displayed when the user wants to quit the page.
+ $w.on( 'beforeunload', this.getConfirmMessage );
+
+ // Hide the "Complete" message.
+ $( '.imagify-row-complete' ).imagifyHide( 200, function() {
+ $( this ).removeClass( 'done' );
+ } );
+
+ // Close the optimization details.
+ $( '.imagify-show-table-details' ).trigger( 'close.imagify' );
+
+ // Make sure to reset properties.
+ this.folderTypesQueue = [];
+ this.status = {};
+ this.displayedWaitMessage = false;
+ this.processIsStopped = false;
+ this.imagifyAction = 'optimize';
+ this.globalGain = 0;
+ this.globalOriginalSize = 0;
+ this.globalOptimizedSize = 0;
+
+ $( '.imagify-bulk-table [name="group[]"]:checked' ).each( function() {
+ var $checkbox = $( this ),
+ $row = $checkbox.closest( '.imagify-row-folder-type' ),
+ groupID = $row.data( 'group-id' ),
+ context = $row.data( 'context' ),
+ level = $row.find( '.imagify-cell-level [name="level[' + groupID + ']"]:checked' ).val();
+
+ // Build the queue.
+ w.imagify.bulk.folderTypesQueue.push( {
+ groupID: groupID,
+ context: context,
+ level: undefined === level ? -1 : parseInt( level, 10 )
+ } );
+
+ // Set the status.
+ w.imagify.bulk.status[ groupID ] = {
+ isError: false,
+ id: 'waiting'
+ };
+
+ // Display a "waiting" message + spinner into the folder rows.
+ if ( skip ) {
+ // No need to do that for the first one, we'll display a "working" row instead.
+ skip = false;
+ return true;
+ }
+
+ // Display the "waiting" folder row and hide the "normal" one.
+ w.imagify.bulk.displayFolderRow( 'waiting', $row );
+ } );
+
+ // Fasten Imagifybeat: 1 tick every 15 seconds, and disable suspend.
+ w.imagify.beat.interval( 15 );
+ w.imagify.beat.disableSuspend();
+
+ // Process the queue.
+ $w.trigger( 'processQueue.imagify' );
+ },
+
+ /*
+ * Process the first item in the queue.
+ */
+ processQueue: function () {
+ var $row, item;
+
+ if ( w.imagify.bulk.processIsStopped ) {
+ return;
+ }
+
+ if ( ! w.imagify.bulk.folderTypesQueue.length ) {
+ $( w ).trigger( 'queueEmpty.imagify' );
+ return;
+ }
+
+ if ( ! w.imagify.bulk.displayedWaitMessage ) {
+ // Display an alert to wait.
+ swal( {
+ title: imagifyBulk.labels.waitTitle,
+ html: imagifyBulk.labels.waitText,
+ showConfirmButton: false,
+ padding: 0,
+ imageUrl: imagifyBulk.waitImageUrl,
+ customClass: 'imagify-sweet-alert'
+ } ).catch( swal.noop );
+ w.imagify.bulk.displayedWaitMessage = true;
+ }
+
+ /**
+ * Fetch files for the first folder type in the queue.
+ */
+ item = w.imagify.bulk.folderTypesQueue.shift();
+ $row = $( '#cb-select-' + item.groupID ).closest( '.imagify-row-folder-type' );
+
+ // Update status.
+ w.imagify.bulk.status[ item.groupID ].id = 'fetching';
+
+ // Display the "working" folder row and hide the "normal" one.
+ w.imagify.bulk.displayFolderRow( 'working', $row );
+
+ // Fetch image IDs.
+ $.get( w.imagify.bulk.getAjaxUrl( 'getMediaIds', item ) )
+ .done( function( response ) {
+ var errorMessage;
+
+ swal.close();
+
+ if ( w.imagify.bulk.processIsStopped ) {
+ return;
+ }
+
+ if ( response.data && response.data.message ) {
+ errorMessage = response.data.message;
+ } else {
+ errorMessage = imagifyBulk.ajaxErrorText;
+ }
+
+ if ( ! response.success ) {
+ // Error.
+ w.imagify.bulk.stopProcess( errorMessage, item );
+ return;
+ }
+
+ if ( ! response.data || ! ( $.isPlainObject( response.data ) || $.isArray( response.data ) ) ) {
+ // Error: should be an array if empty, or an object otherwize.
+ w.imagify.bulk.stopProcess( errorMessage, item );
+ return;
+ }
+
+ // Success.
+ if ( ! $.isEmptyObject( response.data ) ) {
+ // Optimize the files.
+ $( w ).trigger( 'optimizeFiles.imagify', [ item, response.data ] );
+ return;
+ }
+
+ // No images.
+ w.imagify.bulk.status[ item.groupID ].id = 'no-images';
+
+ if ( w.imagify.bulk.hasMultipleRows ) {
+ $( '#cb-select-' + item.groupID ).prop( 'checked', false );
+ }
+
+ if ( ! w.imagify.bulk.folderTypesQueue.length ) {
+ $( w ).trigger( 'queueEmpty.imagify' );
+ return;
+ }
+
+ // Reset the folder row.
+ w.imagify.bulk.displayFolderRow( 'resting', $row );
+
+ $( w ).trigger( 'processQueue.imagify' );
+ } )
+ .fail( function() {
+ // Error.
+ w.imagify.bulk.stopProcess( 'get-unoptimized-images', item );
+ } );
+ },
+
+ /*
+ * Optimize files.
+ *
+ * @param {object} e jQuery's Event object.
+ * @param {object} item Current folder type (from the queue).
+ * @param {object} files A list of file IDs (keys, the IDs are prefixed by an underscore) and URLs (values).
+ */
+ optimizeFiles: function ( e, item, files ) {
+ var $row, $workingRow, $optimizedCount, $errorsCount, $table,
+ $progressBar, $progress, $resultsContainer,
+ optimizedCount, errorsCount, Optimizer,
+ defaultsTemplate = {
+ groupID: item.groupID,
+ mediaID: 0,
+ thumbnail: '', // Preview thumbnail src.
+ filename: '',
+ status: '',
+ icon: '',
+ label: '',
+ thumbnailsCount: '',
+ originalSizeHuman: '',
+ newSizeHuman: '',
+ percentHuman: '',
+ overallSavingHuman: ''
+ };
+
+ if ( w.imagify.bulk.processIsStopped ) {
+ return;
+ }
+
+ $row = $( '#cb-select-' + item.groupID ).closest( '.imagify-row-folder-type' );
+ $workingRow = $row.next( '.imagify-row-working' );
+ $errorsCount = $workingRow.find( '.imagify-cell-count-errors span' );
+ errorsCount = parseInt( $errorsCount.text(), 10 );
+ $table = $row.closest( '.imagify-bulk-table' );
+ $progressBar = $table.find( '.imagify-row-progress' );
+ $progress = $progressBar.find( '.bar' );
+
+ if ( 'optimize' === w.imagify.bulk.imagifyAction ) {
+ $optimizedCount = $workingRow.find( '.imagify-cell-count-optimized span' );
+ optimizedCount = parseInt( $optimizedCount.text(), 10 );
+ }
+
+ // Update folder status.
+ w.imagify.bulk.status[ item.groupID ].id = 'optimizing';
+
+ // Fill in the result table header.
+ $table.find( '.imagify-bulk-table-details thead' ).html( $( '#tmpl-imagify-file-header-' + item.groupID ).html() );
+
+ // Empty the result table body.
+ $resultsContainer = $table.find( '.imagify-bulk-table-details tbody' ).text( '' );
+
+ // Reset and display the progress bar.
+ $progress.css( 'width', '0%' ).find( '.percent' ).text( '0%' );
+ $progressBar.slideDown().attr( 'aria-hidden', 'false' );
+
+ // Optimize the files.
+ Optimizer = new w.imagify.Optimizer( {
+ groupID: item.groupID,
+ context: item.context,
+ level: item.level,
+ bufferSize: imagifyBulk.bufferSizes[ item.context ],
+ ajaxUrl: w.imagify.bulk.getAjaxUrl( 'bulkProcess', item ),
+ files: files,
+ defaultThumb: w.imagify.bulk.defaultThumb,
+ doneEvent: 'mediaProcessed.imagify'
+ } );
+
+ // Before each media optimization, add a file row displaying the optimization process.
+ Optimizer.before( function( data ) {
+ var template;
+
+ if ( w.imagify.bulk.processIsStopped ) {
+ return;
+ }
+
+ template = w.imagify.template( 'imagify-file-row-' + item.groupID );
+
+ w.imagify.bulk.processingMedia.push( {
+ context: item.context,
+ mediaID: data.mediaID
+ } );
+
+ $resultsContainer.prepend( template( $.extend( {}, defaultsTemplate, data, {
+ status: 'compressing',
+ icon: 'admin-generic rotate',
+ label: imagifyBulk.labels.optimizing
+ } ) ) );
+ } );
+
+ // After each media optimization.
+ Optimizer.each( function( data ) {
+ var template, $fileRow;
+
+ if ( w.imagify.bulk.processIsStopped ) {
+ return;
+ }
+
+ template = w.imagify.template( 'imagify-file-row-' + item.groupID );
+ $fileRow = $( '#' + item.groupID + '-' + data.mediaID );
+
+ $.each( w.imagify.bulk.processingMedia, function( i, v ) {
+ if ( v.context !== item.context || v.mediaID !== data.mediaID ) {
+ return true;
+ }
+
+ w.imagify.bulk.processingMedia.splice( i, 1 );
+ return false;
+ } );
+
+ // Update the progress bar.
+ $progress.css( 'width', data.progress + '%' ).find( '.percent' ).html( data.progress + '%' );
+
+ if ( data.success ) {
+ if ( 'already-optimized' !== data.status ) {
+ // Image successfully optimized.
+ $fileRow.replaceWith( template( $.extend( {}, defaultsTemplate, data, {
+ status: 'complete',
+ icon: 'yes',
+ label: imagifyBulk.labels.complete
+ } ) ) );
+
+ w.imagify.bulk.drawFileChart( $( '#' + item.groupID + '-' + data.mediaID ).find( '.imagify-cell-percentage canvas' ) ); // Don't use $fileRow, its DOM is not refreshed with the new values.
+ } else {
+ // The image was already optimized.
+ $fileRow.replaceWith( w.imagify.bulk.displayErrorInRow( template( $.extend( {}, defaultsTemplate, data, {
+ status: 'complete',
+ icon: 'yes',
+ label: imagifyBulk.labels.alreadyOptimized
+ } ) ), data.error ) );
+ }
+
+ // Update the optimized images counter.
+ if ( 'optimize' === w.imagify.bulk.imagifyAction ) {
+ optimizedCount += 1;
+ $optimizedCount.text( optimizedCount );
+ }
+ return;
+ }
+
+ // Display the error in the file row.
+ $fileRow.replaceWith( w.imagify.bulk.displayErrorInRow( template( $.extend( {}, defaultsTemplate, data, {
+ status: 'error',
+ icon: 'dismiss',
+ label: imagifyBulk.labels.error
+ } ) ), data.error || data ) );
+
+ // Update the "working" folder row.
+ if ( ! $errorsCount.length ) {
+ errorsCount = 1;
+ $errorsCount = $workingRow.find( '.imagify-cell-count-errors' ).html( imagifyBulk.labels.imagesErrorText.replace( '%s', '1 ' ) ).find( 'span' );
+ } else {
+ errorsCount += 1;
+ $errorsCount.text( errorsCount );
+ }
+
+ if ( 'over-quota' === data.status ) {
+ // No more data, stop everything.
+ Optimizer.stopProcess();
+ w.imagify.bulk.stopProcess( data.status, item );
+ }
+ } );
+
+ // After all image optimizations.
+ Optimizer.done( function( data ) {
+ // Uncheck the checkbox.
+ if ( w.imagify.bulk.hasMultipleRows ) {
+ $( '#cb-select-' + item.groupID ).prop( 'checked', false );
+ }
+
+ if ( data.globalOriginalSize ) {
+ w.imagify.bulk.globalGain += parseInt( data.globalGain, 10 );
+ w.imagify.bulk.globalOriginalSize += parseInt( data.globalOriginalSize, 10 );
+ w.imagify.bulk.globalOptimizedSize += parseInt( data.globalOptimizedSize, 10 );
+ }
+
+ if ( w.imagify.bulk.processIsStopped ) {
+ return;
+ }
+
+ // Reset Imagifybeat interval and enable suspend.
+ w.imagify.beat.resetInterval();
+ w.imagify.beat.enableSuspend();
+
+ // Update folder type status.
+ if ( ! $.isEmptyObject( w.imagify.bulk.status ) && ! w.imagify.bulk.status[ item.groupID ].isError ) {
+ w.imagify.bulk.status[ item.groupID ].id = 'done';
+ }
+
+ // Update the folder row.
+ $row.addClass( 'updating' );
+
+ $.get( w.imagify.bulk.getAjaxUrl( 'getFolderData', item ) )
+ .done( function( response ) {
+ if ( w.imagify.bulk.processIsStopped ) {
+ return;
+ }
+
+ if ( response.success ) {
+ $.each( response.data, function( dataName, dataHtml ) {
+ $row.children( '.imagify-cell-' + dataName ).html( dataHtml );
+ } );
+ }
+
+ w.imagify.bulk.displayFolderRow( 'resting', $row );
+ } )
+ .always( function() {
+ if ( w.imagify.bulk.processIsStopped ) {
+ return;
+ }
+
+ $row.removeClass( 'updating' );
+
+ if ( ! w.imagify.bulk.folderTypesQueue.length ) {
+ $( w ).trigger( 'queueEmpty.imagify' );
+ } else {
+ $( w ).trigger( 'processQueue.imagify' );
+ }
+ } );
+ } );
+
+ // Run.
+ Optimizer.run();
+ },
+
+ /*
+ * End.
+ */
+ queueEmpty: function () {
+ var $tables = $( '.imagify-bulk-table' ),
+ errorArgs = {},
+ hasError = false,
+ noImages = true,
+ errorMsg = '';
+
+ // Reset Imagifybeat interval and enable suspend.
+ w.imagify.beat.resetInterval();
+ w.imagify.beat.enableSuspend();
+
+ // Display the share box.
+ w.imagify.bulk.displayShareBox();
+
+ // Reset the queue.
+ w.imagify.bulk.folderTypesQueue = [];
+
+ // Fetch and display generic stats if stats via Imagifybeat are disabled.
+ if ( ! imagifyBulk.imagifybeatIDs.stats ) {
+ $.get( w.imagify.bulk.getAjaxUrl( 'getStats' ), {
+ types: w.imagify.bulk.getFolderTypes()
+ } )
+ .done( function( response ) {
+ if ( response.success ) {
+ w.imagify.bulk.updateStats( response.data );
+ }
+ } );
+ }
+
+ // Maybe display error.
+ if ( ! $.isEmptyObject( w.imagify.bulk.status ) ) {
+ $.each( w.imagify.bulk.status, function( groupID, typeStatus ) {
+ if ( typeStatus.isError ) {
+ // One error is enough to display a message.
+ hasError = typeStatus.id;
+ noImages = false;
+ return false;
+ }
+ if ( 'no-images' !== typeStatus.id ) {
+ // All groups must have this ID.
+ noImages = false;
+ return false;
+ }
+ } );
+
+ if ( hasError ) {
+ if ( 'invalid-api-key' === hasError ) {
+ errorArgs = {
+ title: imagifyBulk.labels.invalidAPIKeyTitle,
+ type: 'info'
+ };
+ }
+ else if ( 'over-quota' === hasError ) {
+ errorArgs = {
+ title: imagifyBulk.labels.overQuotaTitle,
+ html: $( '#tmpl-imagify-overquota-alert' ).html(),
+ type: 'info',
+ customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
+ showConfirmButton: false
+ };
+ }
+ else if ( 'get-unoptimized-images' === hasError || 'consumed-all-data' === hasError ) {
+ errorArgs = {
+ title: imagifyBulk.labels.getUnoptimizedImagesErrorTitle,
+ html: imagifyBulk.labels.getUnoptimizedImagesErrorText,
+ type: 'info'
+ };
+ }
+ w.imagify.bulk.displayError( errorArgs );
+ }
+ else if ( noImages ) {
+ if ( imagifyBulk.labels.nothingToDoText.hasOwnProperty( w.imagify.bulk.imagifyAction ) ) {
+ errorMsg = imagifyBulk.labels.nothingToDoText[ w.imagify.bulk.imagifyAction ];
+ } else {
+ errorMsg = imagifyBulk.labels.nothingToDoText.optimize;
+ }
+ w.imagify.bulk.displayError( {
+ title: imagifyBulk.labels.nothingToDoTitle,
+ html: errorMsg,
+ type: 'info'
+ } );
+ }
+ }
+
+ // Reset status.
+ w.imagify.bulk.status = {};
+
+ // Unlink the message displayed when the user wants to quit the page.
+ $( w ).off( 'beforeunload', w.imagify.bulk.getConfirmMessage );
+
+ // Display the "normal" folder rows (the values of the last one should being updated via ajax, don't display it for now).
+ w.imagify.bulk.displayFolderRow( 'resting', $tables.find( '.imagify-row-folder-type' ).not( '.updating' ) );
+
+ // Reset the progress bars.
+ $tables.find( '.imagify-row-progress' ).slideUp().attr( 'aria-hidden', 'true' ).find( '.bar' ).removeAttr( 'style' ).find( '.percent' ).text( '0%' );
+
+ // Enable (or not) the main button.
+ if ( $( '.imagify-bulk-table [name="group[]"]:checked' ).length ) {
+ $( '#imagify-bulk-action' ).removeAttr( 'disabled' ).find( '.dashicons' ).removeClass( 'rotate' );
+ } else {
+ $( '#imagify-bulk-action' ).find( '.dashicons' ).removeClass( 'rotate' );
+ }
+ },
+
+ /**
+ * Open a popup window when the user clicks on a share link.
+ *
+ * @param {object} e jQuery Event object.
+ */
+ share: function ( e ) {
+ var width = 700,
+ height = 290,
+ clientLeft, clientTop;
+
+ e.preventDefault();
+
+ if ( w.innerWidth ) {
+ clientLeft = ( w.innerWidth - width ) / 2;
+ clientTop = ( w.innerHeight - height ) / 2;
+ } else {
+ clientLeft = ( d.body.clientWidth - width ) / 2;
+ clientTop = ( d.body.clientHeight - height ) / 2;
+ }
+
+ w.open( this.href, '', 'status=no, scrollbars=no, menubar=no, top=' + clientTop + ', left=' + clientLeft + ', width=' + width + ', height=' + height );
+ },
+
+ // Imagifybeat =============================================================================
+
+ /**
+ * Add a Imagifybeat ID for global stats on "imagifybeat-send" event.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ addStatsImagifybeat: function ( e, data ) {
+ data[ imagifyBulk.imagifybeatIDs.stats ] = Object.keys( w.imagify.bulk.getFolderTypes() );
+ },
+
+ /**
+ * Listen for the custom event "imagifybeat-tick" on $(document).
+ * It allows to update various data periodically.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ processStatsImagifybeat: function ( e, data ) {
+ if ( typeof data[ imagifyBulk.imagifybeatIDs.stats ] !== 'undefined' ) {
+ w.imagify.bulk.updateStats( data[ imagifyBulk.imagifybeatIDs.stats ] );
+ }
+ },
+
+ /**
+ * Add a Imagifybeat ID on "imagifybeat-send" event to sync the optimization queue.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ addQueueImagifybeat: function ( e, data ) {
+ if ( w.imagify.bulk.processingMedia.length ) {
+ data[ imagifyBulk.imagifybeatIDs.queue ] = w.imagify.bulk.processingMedia;
+ }
+ },
+
+ /**
+ * Listen for the custom event "imagifybeat-tick" on $(document).
+ * It allows to update various data periodically.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ processQueueImagifybeat: function ( e, data ) {
+ if ( typeof data[ imagifyBulk.imagifybeatIDs.queue ] !== 'undefined' ) {
+ $.each( data[ imagifyBulk.imagifybeatIDs.queue ], function ( i, mediaData ) {
+ $( w ).trigger( 'mediaProcessed.imagify', [ mediaData ] );
+ } );
+ }
+ },
+
+ /**
+ * Add a Imagifybeat ID for requirements on "imagifybeat-send" event.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ addRequirementsImagifybeat: function ( e, data ) {
+ data[ imagifyBulk.imagifybeatIDs.requirements ] = 1;
+ },
+
+ /**
+ * Listen for the custom event "imagifybeat-tick" on $(document).
+ * It allows to update requirements status periodically.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ processRequirementsImagifybeat: function ( e, data ) {
+ if ( typeof data[ imagifyBulk.imagifybeatIDs.requirements ] === 'undefined' ) {
+ return;
+ }
+
+ data = data[ imagifyBulk.imagifybeatIDs.requirements ];
+
+ imagifyBulk.curlMissing = data.curl_missing;
+ imagifyBulk.editorMissing = data.editor_missing;
+ imagifyBulk.extHttpBlocked = data.external_http_blocked;
+ imagifyBulk.apiDown = data.api_down;
+ imagifyBulk.keyIsValid = data.key_is_valid;
+ imagifyBulk.isOverQuota = data.is_over_quota;
+ },
+
+ // Charts ==================================================================================
+
+ /**
+ * Overview chart.
+ * Used for the big overview chart.
+ */
+ drawOverviewChart: function ( data ) {
+ var initData, legend;
+
+ if ( ! this.charts.overview.canvas ) {
+ this.charts.overview.canvas = d.getElementById( 'imagify-overview-chart' );
+
+ if ( ! this.charts.overview.canvas ) {
+ return;
+ }
+ }
+
+ data = data && $.isArray( data ) ? data : [];
+
+ if ( this.charts.overview.donut ) {
+ // Update existing donut.
+ if ( data.length ) {
+ if ( data.reduce( function( a, b ) { return a + b; }, 0 ) === 0 ) {
+ data[0] = 1;
+ }
+
+ this.charts.overview.donut.data.datasets[0].data = data;
+ this.charts.overview.donut.update();
+ }
+ return;
+ }
+
+ // Create new donut.
+ this.charts.overview.data.datasets[0].data = [
+ parseInt( this.charts.overview.canvas.getAttribute( 'data-unoptimized' ), 10 ),
+ parseInt( this.charts.overview.canvas.getAttribute( 'data-optimized' ), 10 ),
+ parseInt( this.charts.overview.canvas.getAttribute( 'data-errors' ), 10 )
+ ];
+ initData = $.extend( {}, this.charts.overview.data );
+
+ if ( data.length ) {
+ initData.datasets[0].data = data;
+ }
+
+ if ( initData.datasets[0].data.reduce( function( a, b ) { return a + b; }, 0 ) === 0 ) {
+ initData.datasets[0].data[0] = 1;
+ }
+
+ this.charts.overview.donut = new w.imagify.Chart( this.charts.overview.canvas, {
+ type: 'doughnut',
+ data: initData,
+ options: {
+ legend: {
+ display: false
+ },
+ events: [],
+ animation: {
+ easing: 'easeOutBounce'
+ },
+ tooltips: {
+ displayColors: false,
+ callbacks: {
+ label: function( tooltipItem, localData ) {
+ return localData.datasets[ tooltipItem.datasetIndex ].data[ tooltipItem.index ];
+ }
+ }
+ },
+ responsive: false,
+ cutoutPercentage: 85
+ }
+ } );
+
+ // Then generate the legend and insert it to your page somewhere.
+ legend = '';
+
+ $.each( initData.labels, function( i, label ) {
+ legend += ' ' + label + ' ';
+ } );
+
+ legend += ' ';
+
+ d.getElementById( 'imagify-overview-chart-legend' ).innerHTML = legend;
+ },
+
+ /**
+ * Mini chart.
+ * Used for the charts on each file row.
+ *
+ * @param {element} canvas A jQuery canvas element.
+ */
+ drawFileChart: function ( canvas ) {
+ var donuts = this.charts.files.donuts;
+
+ canvas.each( function() {
+ var value = parseInt( $( this ).closest( '.imagify-chart' ).next( '.imagipercent' ).text().replace( '%', '' ), 10 );
+
+ if ( undefined !== donuts[ this.id ] ) {
+ // Update existing donut.
+ donuts[ this.id ].data.datasets[0].data[0] = value;
+ donuts[ this.id ].data.datasets[0].data[1] = 100 - value;
+ donuts[ this.id ].update();
+ return;
+ }
+
+ // Create new donut.
+ donuts[ this.id ] = new w.imagify.Chart( this, {
+ type: 'doughnut',
+ data: {
+ datasets: [{
+ data: [ value, 100 - value ],
+ backgroundColor: [ '#00B3D3', '#D8D8D8' ],
+ borderColor: '#fff',
+ borderWidth: 1
+ }]
+ },
+ options: {
+ legend: {
+ display: false
+ },
+ events: [],
+ animation: {
+ easing: 'easeOutBounce'
+ },
+ tooltips: {
+ enabled: false
+ },
+ responsive: false
+ }
+ } );
+ } );
+
+ this.charts.files.donuts = donuts;
+ },
+
+ /*
+ * Share Chart.
+ * Used for the chart in the share box.
+ */
+ drawShareChart: function () {
+ var value;
+
+ if ( ! this.charts.share.canvas ) {
+ this.charts.share.canvas = d.getElementById( 'imagify-ac-chart' );
+
+ if ( ! this.charts.share.canvas ) {
+ return;
+ }
+ }
+
+ value = parseInt( $( this.charts.share.canvas ).closest( '.imagify-ac-chart' ).attr( 'data-percent' ), 10 );
+
+ if ( this.charts.share.donut ) {
+ // Update existing donut.
+ this.charts.share.donut.data.datasets[0].data[0] = value;
+ this.charts.share.donut.data.datasets[0].data[1] = 100 - value;
+ this.charts.share.donut.update();
+ return;
+ }
+
+ // Create new donut.
+ this.charts.share.donut = new w.imagify.Chart( this.charts.share.canvas, {
+ type: 'doughnut',
+ data: {
+ datasets: [{
+ data: [ value, 100 - value ],
+ backgroundColor: [ '#40B1D0', '#FFFFFF' ],
+ borderWidth: 0
+ }]
+ },
+ options: {
+ legend: {
+ display: false
+ },
+ events: [],
+ animation: {
+ easing: 'easeOutBounce'
+ },
+ tooltips: {
+ enabled: false
+ },
+ responsive: false,
+ cutoutPercentage: 70
+ }
+ } );
+ }
+ };
+
+ w.imagify.bulk.init();
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/bulk.min.js b/wp-content/plugins/imagify/assets/js/bulk.min.js
new file mode 100644
index 00000000..2061f569
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/bulk.min.js
@@ -0,0 +1 @@
+window.imagify=window.imagify||{},function(a,b){var c=a.propHooks.checked;a.propHooks.checked={set:function(b,d,e){var f;return f=void 0===c?b[e]=d:c(b,d,e),a(b).trigger("change.imagify"),f}},a.fn.imagifyHide=function(b,c){return b&&b>0?this.hide(b,function(){a(this).addClass("hidden").css("display",""),void 0!==c&&c()}):(this.addClass("hidden"),void 0!==c&&c()),this.attr("aria-hidden","true")},a.fn.imagifyShow=function(b,c){return void 0!==c&&c(),b&&b>0?this.show(b,function(){a(this).removeClass("hidden").css("display","")}):this.removeClass("hidden"),this.attr("aria-hidden","false")}}(jQuery),function(a,b,c,d){c.imagify.bulk={charts:{overview:{canvas:!1,donut:!1,data:{labels:[imagifyBulk.labels.overviewChartLabels.unoptimized,imagifyBulk.labels.overviewChartLabels.optimized,imagifyBulk.labels.overviewChartLabels.error],datasets:[{data:[],backgroundColor:["#10121A","#46B1CE","#C51162"],borderWidth:0}]}},files:{donuts:{}},share:{canvas:!1,donut:!1}},folderTypesQueue:[],status:{},displayedWaitMessage:!1,hasMultipleRows:!0,imagifyAction:"",processIsStopped:!1,processingMedia:[],globalGain:0,globalOriginalSize:0,globalOptimizedSize:0,folderTypesData:{},defaultThumb:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhBAMAAAClyt9cAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABtQTFRFR3BMjo6Oh4eHqqqq4uLigoKC+fn5fn5+fHx8SBBv5wAAAAF0Uk5TAEDm2GYAAABWSURBVCjPY2BgEBIEAyERBhgQUoKAIAc0EXXjEDQRRTNzBzRdBokhaGoM2CQc0NQwJJegqWFgM3VAUSNsbGwugCKiqBQUpIAiAgICoyIDKyIIB0JAEQA54jRBweNV0AAAAABJRU5ErkJggg==",init:function(){var d=a(b);this.drawOverviewChart(),this.hasMultipleRows=a('.imagify-bulk-table [name="group[]"]').length>1,a(".imagify-selector-button").on("click.imagify",this.openSelectorFromButton),a(".imagify-selector-list input").on("change.imagify init.imagify",this.syncSelectorFromRadio).filter(":checked").trigger("init.imagify"),d.on("keypress.imagify click.imagify",this.closeSelectors),a('.imagify-bulk-table [name="group[]"]').on("change.imagify init.imagify",this.toggleOptimizationButton).trigger("init.imagify"),a(".imagify-show-table-details").on("click.imagify open.imagify close.imagify",this.toggleOptimizationDetails),a("#imagify-bulk-action").on("click.imagify",this.maybeLaunchAllProcesses),a(".imagify-share-networks a").on("click.imagify",this.share),a(c).on("processQueue.imagify",this.processQueue).on("optimizeFiles.imagify",this.optimizeFiles).on("queueEmpty.imagify",this.queueEmpty),imagifyBulk.ajaxActions.getStats&&a('.imagify-bulk-table [data-group-id="library"][data-context="wp"]').length&&(imagifyBulk.imagifybeatIDs.stats=!1),imagifyBulk.imagifybeatIDs.stats&&d.on("imagifybeat-send",this.addStatsImagifybeat).on("imagifybeat-tick",this.processStatsImagifybeat),d.on("imagifybeat-send",this.addQueueImagifybeat).on("imagifybeat-tick",this.processQueueImagifybeat),d.on("imagifybeat-send",this.addRequirementsImagifybeat).on("imagifybeat-tick",this.processRequirementsImagifybeat)},getAjaxUrl:function(a,b){var d=ajaxurl+c.imagify.concat+"_wpnonce="+imagifyBulk.ajaxNonce+"&action="+imagifyBulk.ajaxActions[a];return b&&b.context&&(d+="&context="+b.context),"getMediaIds"!==a&&"bulkProcess"!==a||(d+="&imagify_action="+c.imagify.bulk.imagifyAction),d},getFolderTypes:function(){return a.isEmptyObject(c.imagify.bulk.folderTypesData)?(a(".imagify-row-folder-type").each(function(){var b=a(this),d={groupID:b.data("group-id"),context:b.data("context")},e=d.groupID+"|"+d.context;c.imagify.bulk.folderTypesData[e]=d}),c.imagify.bulk.folderTypesData):c.imagify.bulk.folderTypesData},getConfirmMessage:function(){return imagifyBulk.labels.processing},closeLevelSelector:function(a,b){if(a&&a.length)return void 0!==b&&b>0?void c.setTimeout(function(){c.imagify.bulk.closeLevelSelector(a)},b):void a.attr("aria-hidden","true")},stopProcess:function(b,d){this.processIsStopped=!0,c.imagify.bulk.status[d.groupID]={isError:!0,id:b},a(c).trigger("queueEmpty.imagify")},hasBlockingError:function(b){return b=void 0!==b&&b,imagifyBulk.curlMissing?(b&&c.imagify.bulk.displayError({html:imagifyBulk.labels.curlMissing}),!0):imagifyBulk.editorMissing?(b&&c.imagify.bulk.displayError({html:imagifyBulk.labels.editorMissing}),!0):imagifyBulk.extHttpBlocked?(b&&c.imagify.bulk.displayError({html:imagifyBulk.labels.extHttpBlocked}),!0):imagifyBulk.apiDown?(b&&c.imagify.bulk.displayError({html:imagifyBulk.labels.apiDown}),!0):imagifyBulk.keyIsValid?!!imagifyBulk.isOverQuota&&(b&&c.imagify.bulk.displayError({title:imagifyBulk.labels.overQuotaTitle,html:a("#tmpl-imagify-overquota-alert").html(),type:"info",customClass:"imagify-swal-has-subtitle imagify-swal-error-header",showConfirmButton:!1}),!0):(b&&c.imagify.bulk.displayError({title:imagifyBulk.labels.invalidAPIKeyTitle,type:"info"}),!0)},displayError:function(b,c,d){var e={title:"",html:"",type:"error",customClass:"",width:620,padding:0,showCloseButton:!0,showConfirmButton:!0};a.isPlainObject(b)?d=a.extend({},e,b):(d=d||{},d=a.extend({},e,{title:b||"",html:c||""},d)),d.title=d.title||imagifyBulk.labels.error,d.customClass+=" imagify-sweet-alert",swal(d).catch(swal.noop)},displayErrorInRow:function(b,c){var d,e;return b=a(b()),d=b.find(".imagify-cell-status ~ td"),e=d.length,c=c||"",d.remove(),b.find(".imagify-cell-status").after(''+c+" "),b},displayFolderRow:function(a,b){var d,e,f,g;return"resting"===a?(b.next(".imagify-row-waiting, .imagify-row-working").remove(),void b.imagifyShow()):(d=b.next(".imagify-row-waiting, .imagify-row-working"),"waiting"===a?(f="#d2d3d6",g=imagifyBulk.labels.waitingOtimizationsText):(f="#40b1d0",g=imagifyBulk.labels.imagesOptimizedText.replace("%s","0 ")),d.length?(d.hasClass("imagify-row-"+a)||(d.attr("class","imagify-row-"+a),d.find(".imagify-cell-checkbox svg").attr("fill",f),d.children(".imagify-cell-count-optimized").html(g)),b.imagifyHide(),void d.imagifyShow()):(d=b.clone().attr({class:"imagify-row-"+a,"aria-hidden":"false"}),e=c.imagify.template("imagify-spinner"),d.children(".imagify-cell-checkbox").html(e()).find("svg").attr("fill",f),d.children(".imagify-cell-title").html(''+d.children(".imagify-cell-title").text()+" "),d.children(".imagify-cell-count-optimized").html(g),d.children(".imagify-cell-count-errors, .imagify-cell-optimized-size, .imagify-cell-original-size, .imagify-cell-level").text(""),void b.imagifyHide().after(d)))},displayShareBox:function(){var b,d,e,f,g=imagifyBulk.labels.textToShare;if(!this.globalGain||this.folderTypesQueue.length)return this.globalGain=0,this.globalOriginalSize=0,void(this.globalOptimizedSize=0);b=(100-this.globalOptimizedSize/this.globalOriginalSize*100).toFixed(2),d=c.imagify.humanSize(this.globalGain,1),e=c.imagify.humanSize(this.globalOriginalSize,1),g=g.replace("%1$s",d),g=g.replace("%2$s",e),g=encodeURIComponent(g),f=a(".imagify-row-complete"),f.find(".imagify-ac-rt-total-gain").html(d),f.find(".imagify-ac-rt-total-original").html(e),f.find(".imagify-ac-chart").attr("data-percent",b),f.find(".imagify-sn-twitter").attr("href",imagifyBulk.labels.twitterShareURL+"&text="+g),this.drawShareChart(),f.addClass("done").imagifyShow(),a("html, body").animate({scrollTop:f.offset().top},200),this.globalGain=0,this.globalOriginalSize=0,this.globalOptimizedSize=0},updateStats:function(b){var d;b&&a.isPlainObject(b)&&(c.imagify.bulk.charts.overview.donut.data&&(d=c.imagify.bulk.charts.overview.donut.data.datasets[0].data,b.unoptimized_attachments===d[0]&&b.optimized_attachments===d[1]&&b.errors_attachments===d[2])||(b.unconsumed_quota=b.unconsumed_quota.toFixed(1),a(".imagify-meteo-icon").html(b.quota_icon),a(".imagify-unconsumed-percent").html(b.unconsumed_quota+"%"),a(".imagify-unconsumed-bar").css("width",b.unconsumed_quota+"%").parent().attr("class",b.quota_class),a("#imagify-overview-chart-percent").html(b.optimized_attachments_percent+"% "),a(".imagify-total-percent").html(b.optimized_attachments_percent+"%"),c.imagify.bulk.drawOverviewChart([b.unoptimized_attachments,b.optimized_attachments,b.errors_attachments]),a("#imagify-total-optimized-attachments").html(b.already_optimized_attachments),a("#imagify-original-bar").find(".imagify-barnb").html(b.original_human),a("#imagify-optimized-bar").css("width",100-b.optimized_percent+"%").find(".imagify-barnb").html(b.optimized_human),a("#imagify-total-optimized-attachments-pct").html(b.optimized_percent+"%")))},openSelectorFromButton:function(b){var c=a("#"+a(this).attr("aria-controls"));b.stopPropagation(),a(".imagify-selector-list").not(c).attr("aria-hidden","true"),c.attr("aria-hidden","false").find(":checked").trigger("focus.imagify")},syncSelectorFromRadio:function(){var b=a(this).closest(".imagify-selector-choice");b.addClass("imagify-selector-current-value").attr("aria-current","true").siblings(".imagify-selector-choice").removeClass("imagify-selector-current-value").attr("aria-current","false"),b.closest(".imagify-selector-list").siblings(".imagify-selector-button").find(".imagify-selector-current-value-info").html(b.find("label").html())},closeSelectors:function(b){"keypress"===b.type&&27!==b.keyCode&&13!==b.keyCode||c.imagify.bulk.closeLevelSelector(a('.imagify-selector-list[aria-hidden="false"]'))},toggleOptimizationButton:function(){if(!c.imagify.bulk.hasMultipleRows&&!this.checked)return void a(this).prop("checked",!0);a('.imagify-bulk-table [name="group[]"]:checked').length?a("#imagify-bulk-action").removeAttr("disabled"):a("#imagify-bulk-action").attr("disabled","disabled")},toggleOptimizationDetails:function(b){var c,d=a(this),e=d.closest(".imagify-bulk-table").find(".imagify-bulk-table-details");c="open"===b.type||"close"!==b.type&&e.hasClass("hidden"),c?(d.html(d.data("label-hide")+' '),e.imagifyShow()):(d.html(d.data("label-show")+''),e.imagifyHide())},maybeLaunchAllProcesses:function(){var b;if(!a(this).attr("disabled")&&a('.imagify-bulk-table [name="group[]"]:checked').length&&!c.imagify.bulk.hasBlockingError(!0)){if(b=a("#tmpl-imagify-bulk-infos"),!b.length)return void c.imagify.bulk.launchAllProcesses();swal({title:imagifyBulk.labels.bulkInfoTitle,html:b.html(),type:"",customClass:"imagify-sweet-alert imagify-swal-has-subtitle imagify-before-bulk-infos",showCancelButton:!0,padding:0,width:554,confirmButtonText:imagifyBulk.labels.confirmBulk,cancelButtonText:imagifySwal.labels.cancelButtonText,reverseButtons:!0}).then(function(){var d=a('.imagify-bulk-table [name="group[]"]:checked').first().closest(".imagify-row-folder-type");a.get(c.imagify.bulk.getAjaxUrl("bulkInfoSeen",{context:d.data("context")})),b.remove(),c.imagify.bulk.launchAllProcesses()}).catch(swal.noop)}},launchAllProcesses:function(){var b=a(c),d=a("#imagify-bulk-action"),e=!0;d.attr("disabled","disabled").find(".dashicons").addClass("rotate"),b.on("beforeunload",this.getConfirmMessage),a(".imagify-row-complete").imagifyHide(200,function(){a(this).removeClass("done")}),a(".imagify-show-table-details").trigger("close.imagify"),this.folderTypesQueue=[],this.status={},this.displayedWaitMessage=!1,this.processIsStopped=!1,this.imagifyAction="optimize",this.globalGain=0,this.globalOriginalSize=0,this.globalOptimizedSize=0,a('.imagify-bulk-table [name="group[]"]:checked').each(function(){var b=a(this),d=b.closest(".imagify-row-folder-type"),f=d.data("group-id"),g=d.data("context"),h=d.find('.imagify-cell-level [name="level['+f+']"]:checked').val();if(c.imagify.bulk.folderTypesQueue.push({groupID:f,context:g,level:void 0===h?-1:parseInt(h,10)}),c.imagify.bulk.status[f]={isError:!1,id:"waiting"},e)return e=!1,!0;c.imagify.bulk.displayFolderRow("waiting",d)}),c.imagify.beat.interval(15),c.imagify.beat.disableSuspend(),b.trigger("processQueue.imagify")},processQueue:function(){var b,d;if(!c.imagify.bulk.processIsStopped){if(!c.imagify.bulk.folderTypesQueue.length)return void a(c).trigger("queueEmpty.imagify");c.imagify.bulk.displayedWaitMessage||(swal({title:imagifyBulk.labels.waitTitle,html:imagifyBulk.labels.waitText,showConfirmButton:!1,padding:0,imageUrl:imagifyBulk.waitImageUrl,customClass:"imagify-sweet-alert"}).catch(swal.noop),c.imagify.bulk.displayedWaitMessage=!0),d=c.imagify.bulk.folderTypesQueue.shift(),b=a("#cb-select-"+d.groupID).closest(".imagify-row-folder-type"),c.imagify.bulk.status[d.groupID].id="fetching",c.imagify.bulk.displayFolderRow("working",b),a.get(c.imagify.bulk.getAjaxUrl("getMediaIds",d)).done(function(e){var f;if(swal.close(),!c.imagify.bulk.processIsStopped){if(f=e.data&&e.data.message?e.data.message:imagifyBulk.ajaxErrorText,!e.success)return void c.imagify.bulk.stopProcess(f,d);if(!e.data||!a.isPlainObject(e.data)&&!a.isArray(e.data))return void c.imagify.bulk.stopProcess(f,d);if(!a.isEmptyObject(e.data))return void a(c).trigger("optimizeFiles.imagify",[d,e.data]);if(c.imagify.bulk.status[d.groupID].id="no-images",c.imagify.bulk.hasMultipleRows&&a("#cb-select-"+d.groupID).prop("checked",!1),!c.imagify.bulk.folderTypesQueue.length)return void a(c).trigger("queueEmpty.imagify");c.imagify.bulk.displayFolderRow("resting",b),a(c).trigger("processQueue.imagify")}}).fail(function(){c.imagify.bulk.stopProcess("get-unoptimized-images",d)})}},optimizeFiles:function(b,d,e){var f,g,h,i,j,k,l,m,n,o,p,q={groupID:d.groupID,mediaID:0,thumbnail:"",filename:"",status:"",icon:"",label:"",thumbnailsCount:"",originalSizeHuman:"",newSizeHuman:"",percentHuman:"",overallSavingHuman:""};c.imagify.bulk.processIsStopped||(f=a("#cb-select-"+d.groupID).closest(".imagify-row-folder-type"),g=f.next(".imagify-row-working"),i=g.find(".imagify-cell-count-errors span"),o=parseInt(i.text(),10),j=f.closest(".imagify-bulk-table"),k=j.find(".imagify-row-progress"),l=k.find(".bar"),"optimize"===c.imagify.bulk.imagifyAction&&(h=g.find(".imagify-cell-count-optimized span"),n=parseInt(h.text(),10)),c.imagify.bulk.status[d.groupID].id="optimizing",j.find(".imagify-bulk-table-details thead").html(a("#tmpl-imagify-file-header-"+d.groupID).html()),m=j.find(".imagify-bulk-table-details tbody").text(""),l.css("width","0%").find(".percent").text("0%"),k.slideDown().attr("aria-hidden","false"),p=new c.imagify.Optimizer({groupID:d.groupID,context:d.context,level:d.level,bufferSize:imagifyBulk.bufferSizes[d.context],ajaxUrl:c.imagify.bulk.getAjaxUrl("bulkProcess",d),files:e,defaultThumb:c.imagify.bulk.defaultThumb,doneEvent:"mediaProcessed.imagify"}),p.before(function(b){var e;c.imagify.bulk.processIsStopped||(e=c.imagify.template("imagify-file-row-"+d.groupID),c.imagify.bulk.processingMedia.push({context:d.context,mediaID:b.mediaID}),m.prepend(e(a.extend({},q,b,{status:"compressing",icon:"admin-generic rotate",label:imagifyBulk.labels.optimizing}))))}),p.each(function(b){var e,f;if(!c.imagify.bulk.processIsStopped){if(e=c.imagify.template("imagify-file-row-"+d.groupID),f=a("#"+d.groupID+"-"+b.mediaID),a.each(c.imagify.bulk.processingMedia,function(a,e){return e.context!==d.context||e.mediaID!==b.mediaID||(c.imagify.bulk.processingMedia.splice(a,1),!1)}),l.css("width",b.progress+"%").find(".percent").html(b.progress+"%"),b.success)return"already-optimized"!==b.status?(f.replaceWith(e(a.extend({},q,b,{status:"complete",icon:"yes",label:imagifyBulk.labels.complete}))),c.imagify.bulk.drawFileChart(a("#"+d.groupID+"-"+b.mediaID).find(".imagify-cell-percentage canvas"))):f.replaceWith(c.imagify.bulk.displayErrorInRow(e(a.extend({},q,b,{status:"complete",icon:"yes",label:imagifyBulk.labels.alreadyOptimized})),b.error)),void("optimize"===c.imagify.bulk.imagifyAction&&(n+=1,h.text(n)));f.replaceWith(c.imagify.bulk.displayErrorInRow(e(a.extend({},q,b,{status:"error",icon:"dismiss",label:imagifyBulk.labels.error})),b.error||b)),i.length?(o+=1,i.text(o)):(o=1,i=g.find(".imagify-cell-count-errors").html(imagifyBulk.labels.imagesErrorText.replace("%s","1 ")).find("span")),"over-quota"===b.status&&(p.stopProcess(),c.imagify.bulk.stopProcess(b.status,d))}}),p.done(function(b){c.imagify.bulk.hasMultipleRows&&a("#cb-select-"+d.groupID).prop("checked",!1),b.globalOriginalSize&&(c.imagify.bulk.globalGain+=parseInt(b.globalGain,10),c.imagify.bulk.globalOriginalSize+=parseInt(b.globalOriginalSize,10),c.imagify.bulk.globalOptimizedSize+=parseInt(b.globalOptimizedSize,10)),c.imagify.bulk.processIsStopped||(c.imagify.beat.resetInterval(),c.imagify.beat.enableSuspend(),a.isEmptyObject(c.imagify.bulk.status)||c.imagify.bulk.status[d.groupID].isError||(c.imagify.bulk.status[d.groupID].id="done"),f.addClass("updating"),a.get(c.imagify.bulk.getAjaxUrl("getFolderData",d)).done(function(b){c.imagify.bulk.processIsStopped||(b.success&&a.each(b.data,function(a,b){f.children(".imagify-cell-"+a).html(b)}),c.imagify.bulk.displayFolderRow("resting",f))}).always(function(){c.imagify.bulk.processIsStopped||(f.removeClass("updating"),c.imagify.bulk.folderTypesQueue.length?a(c).trigger("processQueue.imagify"):a(c).trigger("queueEmpty.imagify"))}))}),p.run())},queueEmpty:function(){var b=a(".imagify-bulk-table"),d={},e=!1,f=!0,g="";c.imagify.beat.resetInterval(),c.imagify.beat.enableSuspend(),c.imagify.bulk.displayShareBox(),c.imagify.bulk.folderTypesQueue=[],imagifyBulk.imagifybeatIDs.stats||a.get(c.imagify.bulk.getAjaxUrl("getStats"),{types:c.imagify.bulk.getFolderTypes()}).done(function(a){a.success&&c.imagify.bulk.updateStats(a.data)}),a.isEmptyObject(c.imagify.bulk.status)||(a.each(c.imagify.bulk.status,function(a,b){return b.isError?(e=b.id,f=!1,!1):"no-images"!==b.id?(f=!1,!1):void 0}),e?("invalid-api-key"===e?d={title:imagifyBulk.labels.invalidAPIKeyTitle,type:"info"}:"over-quota"===e?d={title:imagifyBulk.labels.overQuotaTitle,html:a("#tmpl-imagify-overquota-alert").html(),type:"info",customClass:"imagify-swal-has-subtitle imagify-swal-error-header",showConfirmButton:!1}:"get-unoptimized-images"!==e&&"consumed-all-data"!==e||(d={title:imagifyBulk.labels.getUnoptimizedImagesErrorTitle,html:imagifyBulk.labels.getUnoptimizedImagesErrorText,type:"info"}),c.imagify.bulk.displayError(d)):f&&(g=imagifyBulk.labels.nothingToDoText.hasOwnProperty(c.imagify.bulk.imagifyAction)?imagifyBulk.labels.nothingToDoText[c.imagify.bulk.imagifyAction]:imagifyBulk.labels.nothingToDoText.optimize,c.imagify.bulk.displayError({title:imagifyBulk.labels.nothingToDoTitle,html:g,type:"info"}))),c.imagify.bulk.status={},a(c).off("beforeunload",c.imagify.bulk.getConfirmMessage),c.imagify.bulk.displayFolderRow("resting",b.find(".imagify-row-folder-type").not(".updating")),b.find(".imagify-row-progress").slideUp().attr("aria-hidden","true").find(".bar").removeAttr("style").find(".percent").text("0%"),a('.imagify-bulk-table [name="group[]"]:checked').length?a("#imagify-bulk-action").removeAttr("disabled").find(".dashicons").removeClass("rotate"):a("#imagify-bulk-action").find(".dashicons").removeClass("rotate")},share:function(a){var d,e;a.preventDefault(),c.innerWidth?(d=(c.innerWidth-700)/2,e=(c.innerHeight-290)/2):(d=(b.body.clientWidth-700)/2,e=(b.body.clientHeight-290)/2),c.open(this.href,"","status=no, scrollbars=no, menubar=no, top="+e+", left="+d+", width=700, height=290")},addStatsImagifybeat:function(a,b){b[imagifyBulk.imagifybeatIDs.stats]=Object.keys(c.imagify.bulk.getFolderTypes())},processStatsImagifybeat:function(a,b){void 0!==b[imagifyBulk.imagifybeatIDs.stats]&&c.imagify.bulk.updateStats(b[imagifyBulk.imagifybeatIDs.stats])},addQueueImagifybeat:function(a,b){c.imagify.bulk.processingMedia.length&&(b[imagifyBulk.imagifybeatIDs.queue]=c.imagify.bulk.processingMedia)},processQueueImagifybeat:function(b,d){void 0!==d[imagifyBulk.imagifybeatIDs.queue]&&a.each(d[imagifyBulk.imagifybeatIDs.queue],function(b,d){a(c).trigger("mediaProcessed.imagify",[d])})},addRequirementsImagifybeat:function(a,b){b[imagifyBulk.imagifybeatIDs.requirements]=1},processRequirementsImagifybeat:function(a,b){void 0!==b[imagifyBulk.imagifybeatIDs.requirements]&&(b=b[imagifyBulk.imagifybeatIDs.requirements],imagifyBulk.curlMissing=b.curl_missing,imagifyBulk.editorMissing=b.editor_missing,imagifyBulk.extHttpBlocked=b.external_http_blocked,imagifyBulk.apiDown=b.api_down,imagifyBulk.keyIsValid=b.key_is_valid,imagifyBulk.isOverQuota=b.is_over_quota)},drawOverviewChart:function(d){var e,f;if(this.charts.overview.canvas||(this.charts.overview.canvas=b.getElementById("imagify-overview-chart"),this.charts.overview.canvas)){if(d=d&&a.isArray(d)?d:[],this.charts.overview.donut)return void(d.length&&(0===d.reduce(function(a,b){return a+b},0)&&(d[0]=1),this.charts.overview.donut.data.datasets[0].data=d,this.charts.overview.donut.update()));this.charts.overview.data.datasets[0].data=[parseInt(this.charts.overview.canvas.getAttribute("data-unoptimized"),10),parseInt(this.charts.overview.canvas.getAttribute("data-optimized"),10),parseInt(this.charts.overview.canvas.getAttribute("data-errors"),10)],e=a.extend({},this.charts.overview.data),d.length&&(e.datasets[0].data=d),0===e.datasets[0].data.reduce(function(a,b){return a+b},0)&&(e.datasets[0].data[0]=1),this.charts.overview.donut=new c.imagify.Chart(this.charts.overview.canvas,{type:"doughnut",data:e,options:{legend:{display:!1},events:[],animation:{easing:"easeOutBounce"},tooltips:{displayColors:!1,callbacks:{label:function(a,b){return b.datasets[a.datasetIndex].data[a.index]}}},responsive:!1,cutoutPercentage:85}}),f='',a.each(e.labels,function(a,b){f+=' '+b+" "}),f+=" ",b.getElementById("imagify-overview-chart-legend").innerHTML=f}},drawFileChart:function(b){var d=this.charts.files.donuts;b.each(function(){var b=parseInt(a(this).closest(".imagify-chart").next(".imagipercent").text().replace("%",""),10);if(void 0!==d[this.id])return d[this.id].data.datasets[0].data[0]=b,d[this.id].data.datasets[0].data[1]=100-b,void d[this.id].update();d[this.id]=new c.imagify.Chart(this,{type:"doughnut",data:{datasets:[{data:[b,100-b],backgroundColor:["#00B3D3","#D8D8D8"],borderColor:"#fff",borderWidth:1}]},options:{legend:{display:!1},events:[],animation:{easing:"easeOutBounce"},tooltips:{enabled:!1},responsive:!1}})}),this.charts.files.donuts=d},drawShareChart:function(){var d;if(this.charts.share.canvas||(this.charts.share.canvas=b.getElementById("imagify-ac-chart"),this.charts.share.canvas)){if(d=parseInt(a(this.charts.share.canvas).closest(".imagify-ac-chart").attr("data-percent"),10),this.charts.share.donut)return this.charts.share.donut.data.datasets[0].data[0]=d,this.charts.share.donut.data.datasets[0].data[1]=100-d,void this.charts.share.donut.update();this.charts.share.donut=new c.imagify.Chart(this.charts.share.canvas,{type:"doughnut",data:{datasets:[{data:[d,100-d],backgroundColor:["#40B1D0","#FFFFFF"],borderWidth:0}]},options:{legend:{display:!1},events:[],animation:{easing:"easeOutBounce"},tooltips:{enabled:!1},responsive:!1,cutoutPercentage:70}})}}},c.imagify.bulk.init()}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/chart.js b/wp-content/plugins/imagify/assets/js/chart.js
new file mode 100644
index 00000000..14cd0e6c
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/chart.js
@@ -0,0 +1,14146 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 2.7.1
+ *
+ * Copyright 2017 Nick Downie
+ * Released under the MIT license
+ * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
+ */
+window.imagify = window.imagify || {};
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window.imagify}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Chart = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o lum2) {
+ return (lum1 + 0.05) / (lum2 + 0.05);
+ }
+ return (lum2 + 0.05) / (lum1 + 0.05);
+ },
+
+ level: function (color2) {
+ var contrastRatio = this.contrast(color2);
+ if (contrastRatio >= 7.1) {
+ return 'AAA';
+ }
+
+ return (contrastRatio >= 4.5) ? 'AA' : '';
+ },
+
+ dark: function () {
+ // YIQ equation from http://24ways.org/2010/calculating-color-contrast
+ var rgb = this.values.rgb;
+ var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
+ return yiq < 128;
+ },
+
+ light: function () {
+ return !this.dark();
+ },
+
+ negate: function () {
+ var rgb = [];
+ for (var i = 0; i < 3; i++) {
+ rgb[i] = 255 - this.values.rgb[i];
+ }
+ this.setValues('rgb', rgb);
+ return this;
+ },
+
+ lighten: function (ratio) {
+ var hsl = this.values.hsl;
+ hsl[2] += hsl[2] * ratio;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ darken: function (ratio) {
+ var hsl = this.values.hsl;
+ hsl[2] -= hsl[2] * ratio;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ saturate: function (ratio) {
+ var hsl = this.values.hsl;
+ hsl[1] += hsl[1] * ratio;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ desaturate: function (ratio) {
+ var hsl = this.values.hsl;
+ hsl[1] -= hsl[1] * ratio;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ whiten: function (ratio) {
+ var hwb = this.values.hwb;
+ hwb[1] += hwb[1] * ratio;
+ this.setValues('hwb', hwb);
+ return this;
+ },
+
+ blacken: function (ratio) {
+ var hwb = this.values.hwb;
+ hwb[2] += hwb[2] * ratio;
+ this.setValues('hwb', hwb);
+ return this;
+ },
+
+ greyscale: function () {
+ var rgb = this.values.rgb;
+ // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
+ var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
+ this.setValues('rgb', [val, val, val]);
+ return this;
+ },
+
+ clearer: function (ratio) {
+ var alpha = this.values.alpha;
+ this.setValues('alpha', alpha - (alpha * ratio));
+ return this;
+ },
+
+ opaquer: function (ratio) {
+ var alpha = this.values.alpha;
+ this.setValues('alpha', alpha + (alpha * ratio));
+ return this;
+ },
+
+ rotate: function (degrees) {
+ var hsl = this.values.hsl;
+ var hue = (hsl[0] + degrees) % 360;
+ hsl[0] = hue < 0 ? 360 + hue : hue;
+ this.setValues('hsl', hsl);
+ return this;
+ },
+
+ /**
+ * Ported from sass implementation in C
+ * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
+ */
+ mix: function (mixinColor, weight) {
+ var color1 = this;
+ var color2 = mixinColor;
+ var p = weight === undefined ? 0.5 : weight;
+
+ var w = 2 * p - 1;
+ var a = color1.alpha() - color2.alpha();
+
+ var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
+ var w2 = 1 - w1;
+
+ return this
+ .rgb(
+ w1 * color1.red() + w2 * color2.red(),
+ w1 * color1.green() + w2 * color2.green(),
+ w1 * color1.blue() + w2 * color2.blue()
+ )
+ .alpha(color1.alpha() * p + color2.alpha() * (1 - p));
+ },
+
+ toJSON: function () {
+ return this.rgb();
+ },
+
+ clone: function () {
+ // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
+ // making the final build way to big to embed in Chart.js. So let's do it manually,
+ // assuming that values to clone are 1 dimension arrays containing only numbers,
+ // except 'alpha' which is a number.
+ var result = new Color();
+ var source = this.values;
+ var target = result.values;
+ var value, type;
+
+ for (var prop in source) {
+ if (source.hasOwnProperty(prop)) {
+ value = source[prop];
+ type = ({}).toString.call(value);
+ if (type === '[object Array]') {
+ target[prop] = value.slice(0);
+ } else if (type === '[object Number]') {
+ target[prop] = value;
+ } else {
+ console.error('unexpected color value:', value);
+ }
+ }
+ }
+
+ return result;
+ }
+};
+
+Color.prototype.spaces = {
+ rgb: ['red', 'green', 'blue'],
+ hsl: ['hue', 'saturation', 'lightness'],
+ hsv: ['hue', 'saturation', 'value'],
+ hwb: ['hue', 'whiteness', 'blackness'],
+ cmyk: ['cyan', 'magenta', 'yellow', 'black']
+};
+
+Color.prototype.maxes = {
+ rgb: [255, 255, 255],
+ hsl: [360, 100, 100],
+ hsv: [360, 100, 100],
+ hwb: [360, 100, 100],
+ cmyk: [100, 100, 100, 100]
+};
+
+Color.prototype.getValues = function (space) {
+ var values = this.values;
+ var vals = {};
+
+ for (var i = 0; i < space.length; i++) {
+ vals[space.charAt(i)] = values[space][i];
+ }
+
+ if (values.alpha !== 1) {
+ vals.a = values.alpha;
+ }
+
+ // {r: 255, g: 255, b: 255, a: 0.4}
+ return vals;
+};
+
+Color.prototype.setValues = function (space, vals) {
+ var values = this.values;
+ var spaces = this.spaces;
+ var maxes = this.maxes;
+ var alpha = 1;
+ var i;
+
+ this.valid = true;
+
+ if (space === 'alpha') {
+ alpha = vals;
+ } else if (vals.length) {
+ // [10, 10, 10]
+ values[space] = vals.slice(0, space.length);
+ alpha = vals[space.length];
+ } else if (vals[space.charAt(0)] !== undefined) {
+ // {r: 10, g: 10, b: 10}
+ for (i = 0; i < space.length; i++) {
+ values[space][i] = vals[space.charAt(i)];
+ }
+
+ alpha = vals.a;
+ } else if (vals[spaces[space][0]] !== undefined) {
+ // {red: 10, green: 10, blue: 10}
+ var chans = spaces[space];
+
+ for (i = 0; i < space.length; i++) {
+ values[space][i] = vals[chans[i]];
+ }
+
+ alpha = vals.alpha;
+ }
+
+ values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
+
+ if (space === 'alpha') {
+ return false;
+ }
+
+ var capped;
+
+ // cap values of the space prior converting all values
+ for (i = 0; i < space.length; i++) {
+ capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
+ values[space][i] = Math.round(capped);
+ }
+
+ // convert to all the other color spaces
+ for (var sname in spaces) {
+ if (sname !== space) {
+ values[sname] = convert[space][sname](values[space]);
+ }
+ }
+
+ return true;
+};
+
+Color.prototype.setSpace = function (space, args) {
+ var vals = args[0];
+
+ if (vals === undefined) {
+ // color.rgb()
+ return this.getValues(space);
+ }
+
+ // color.rgb(10, 10, 10)
+ if (typeof vals === 'number') {
+ vals = Array.prototype.slice.call(args);
+ }
+
+ this.setValues(space, vals);
+ return this;
+};
+
+Color.prototype.setChannel = function (space, index, val) {
+ var svalues = this.values[space];
+ if (val === undefined) {
+ // color.red()
+ return svalues[index];
+ } else if (val === svalues[index]) {
+ // color.red(color.red())
+ return this;
+ }
+
+ // color.red(100)
+ svalues[index] = val;
+ this.setValues(space, svalues);
+
+ return this;
+};
+
+if (typeof window !== 'undefined') {
+ window.imagify.Color = Color;
+}
+
+module.exports = Color;
+
+},{"2":2,"5":5}],4:[function(require,module,exports){
+/* MIT license */
+
+module.exports = {
+ rgb2hsl: rgb2hsl,
+ rgb2hsv: rgb2hsv,
+ rgb2hwb: rgb2hwb,
+ rgb2cmyk: rgb2cmyk,
+ rgb2keyword: rgb2keyword,
+ rgb2xyz: rgb2xyz,
+ rgb2lab: rgb2lab,
+ rgb2lch: rgb2lch,
+
+ hsl2rgb: hsl2rgb,
+ hsl2hsv: hsl2hsv,
+ hsl2hwb: hsl2hwb,
+ hsl2cmyk: hsl2cmyk,
+ hsl2keyword: hsl2keyword,
+
+ hsv2rgb: hsv2rgb,
+ hsv2hsl: hsv2hsl,
+ hsv2hwb: hsv2hwb,
+ hsv2cmyk: hsv2cmyk,
+ hsv2keyword: hsv2keyword,
+
+ hwb2rgb: hwb2rgb,
+ hwb2hsl: hwb2hsl,
+ hwb2hsv: hwb2hsv,
+ hwb2cmyk: hwb2cmyk,
+ hwb2keyword: hwb2keyword,
+
+ cmyk2rgb: cmyk2rgb,
+ cmyk2hsl: cmyk2hsl,
+ cmyk2hsv: cmyk2hsv,
+ cmyk2hwb: cmyk2hwb,
+ cmyk2keyword: cmyk2keyword,
+
+ keyword2rgb: keyword2rgb,
+ keyword2hsl: keyword2hsl,
+ keyword2hsv: keyword2hsv,
+ keyword2hwb: keyword2hwb,
+ keyword2cmyk: keyword2cmyk,
+ keyword2lab: keyword2lab,
+ keyword2xyz: keyword2xyz,
+
+ xyz2rgb: xyz2rgb,
+ xyz2lab: xyz2lab,
+ xyz2lch: xyz2lch,
+
+ lab2xyz: lab2xyz,
+ lab2rgb: lab2rgb,
+ lab2lch: lab2lch,
+
+ lch2lab: lch2lab,
+ lch2xyz: lch2xyz,
+ lch2rgb: lch2rgb
+}
+
+
+function rgb2hsl(rgb) {
+ var r = rgb[0]/255,
+ g = rgb[1]/255,
+ b = rgb[2]/255,
+ min = Math.min(r, g, b),
+ max = Math.max(r, g, b),
+ delta = max - min,
+ h, s, l;
+
+ if (max == min)
+ h = 0;
+ else if (r == max)
+ h = (g - b) / delta;
+ else if (g == max)
+ h = 2 + (b - r) / delta;
+ else if (b == max)
+ h = 4 + (r - g)/ delta;
+
+ h = Math.min(h * 60, 360);
+
+ if (h < 0)
+ h += 360;
+
+ l = (min + max) / 2;
+
+ if (max == min)
+ s = 0;
+ else if (l <= 0.5)
+ s = delta / (max + min);
+ else
+ s = delta / (2 - max - min);
+
+ return [h, s * 100, l * 100];
+}
+
+function rgb2hsv(rgb) {
+ var r = rgb[0],
+ g = rgb[1],
+ b = rgb[2],
+ min = Math.min(r, g, b),
+ max = Math.max(r, g, b),
+ delta = max - min,
+ h, s, v;
+
+ if (max == 0)
+ s = 0;
+ else
+ s = (delta/max * 1000)/10;
+
+ if (max == min)
+ h = 0;
+ else if (r == max)
+ h = (g - b) / delta;
+ else if (g == max)
+ h = 2 + (b - r) / delta;
+ else if (b == max)
+ h = 4 + (r - g) / delta;
+
+ h = Math.min(h * 60, 360);
+
+ if (h < 0)
+ h += 360;
+
+ v = ((max / 255) * 1000) / 10;
+
+ return [h, s, v];
+}
+
+function rgb2hwb(rgb) {
+ var r = rgb[0],
+ g = rgb[1],
+ b = rgb[2],
+ h = rgb2hsl(rgb)[0],
+ w = 1/255 * Math.min(r, Math.min(g, b)),
+ b = 1 - 1/255 * Math.max(r, Math.max(g, b));
+
+ return [h, w * 100, b * 100];
+}
+
+function rgb2cmyk(rgb) {
+ var r = rgb[0] / 255,
+ g = rgb[1] / 255,
+ b = rgb[2] / 255,
+ c, m, y, k;
+
+ k = Math.min(1 - r, 1 - g, 1 - b);
+ c = (1 - r - k) / (1 - k) || 0;
+ m = (1 - g - k) / (1 - k) || 0;
+ y = (1 - b - k) / (1 - k) || 0;
+ return [c * 100, m * 100, y * 100, k * 100];
+}
+
+function rgb2keyword(rgb) {
+ return reverseKeywords[JSON.stringify(rgb)];
+}
+
+function rgb2xyz(rgb) {
+ var r = rgb[0] / 255,
+ g = rgb[1] / 255,
+ b = rgb[2] / 255;
+
+ // assume sRGB
+ r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
+ g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
+ b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
+
+ var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
+ var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
+ var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
+
+ return [x * 100, y *100, z * 100];
+}
+
+function rgb2lab(rgb) {
+ var xyz = rgb2xyz(rgb),
+ x = xyz[0],
+ y = xyz[1],
+ z = xyz[2],
+ l, a, b;
+
+ x /= 95.047;
+ y /= 100;
+ z /= 108.883;
+
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
+
+ l = (116 * y) - 16;
+ a = 500 * (x - y);
+ b = 200 * (y - z);
+
+ return [l, a, b];
+}
+
+function rgb2lch(args) {
+ return lab2lch(rgb2lab(args));
+}
+
+function hsl2rgb(hsl) {
+ var h = hsl[0] / 360,
+ s = hsl[1] / 100,
+ l = hsl[2] / 100,
+ t1, t2, t3, rgb, val;
+
+ if (s == 0) {
+ val = l * 255;
+ return [val, val, val];
+ }
+
+ if (l < 0.5)
+ t2 = l * (1 + s);
+ else
+ t2 = l + s - l * s;
+ t1 = 2 * l - t2;
+
+ rgb = [0, 0, 0];
+ for (var i = 0; i < 3; i++) {
+ t3 = h + 1 / 3 * - (i - 1);
+ t3 < 0 && t3++;
+ t3 > 1 && t3--;
+
+ if (6 * t3 < 1)
+ val = t1 + (t2 - t1) * 6 * t3;
+ else if (2 * t3 < 1)
+ val = t2;
+ else if (3 * t3 < 2)
+ val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
+ else
+ val = t1;
+
+ rgb[i] = val * 255;
+ }
+
+ return rgb;
+}
+
+function hsl2hsv(hsl) {
+ var h = hsl[0],
+ s = hsl[1] / 100,
+ l = hsl[2] / 100,
+ sv, v;
+
+ if(l === 0) {
+ // no need to do calc on black
+ // also avoids divide by 0 error
+ return [0, 0, 0];
+ }
+
+ l *= 2;
+ s *= (l <= 1) ? l : 2 - l;
+ v = (l + s) / 2;
+ sv = (2 * s) / (l + s);
+ return [h, sv * 100, v * 100];
+}
+
+function hsl2hwb(args) {
+ return rgb2hwb(hsl2rgb(args));
+}
+
+function hsl2cmyk(args) {
+ return rgb2cmyk(hsl2rgb(args));
+}
+
+function hsl2keyword(args) {
+ return rgb2keyword(hsl2rgb(args));
+}
+
+
+function hsv2rgb(hsv) {
+ var h = hsv[0] / 60,
+ s = hsv[1] / 100,
+ v = hsv[2] / 100,
+ hi = Math.floor(h) % 6;
+
+ var f = h - Math.floor(h),
+ p = 255 * v * (1 - s),
+ q = 255 * v * (1 - (s * f)),
+ t = 255 * v * (1 - (s * (1 - f))),
+ v = 255 * v;
+
+ switch(hi) {
+ case 0:
+ return [v, t, p];
+ case 1:
+ return [q, v, p];
+ case 2:
+ return [p, v, t];
+ case 3:
+ return [p, q, v];
+ case 4:
+ return [t, p, v];
+ case 5:
+ return [v, p, q];
+ }
+}
+
+function hsv2hsl(hsv) {
+ var h = hsv[0],
+ s = hsv[1] / 100,
+ v = hsv[2] / 100,
+ sl, l;
+
+ l = (2 - s) * v;
+ sl = s * v;
+ sl /= (l <= 1) ? l : 2 - l;
+ sl = sl || 0;
+ l /= 2;
+ return [h, sl * 100, l * 100];
+}
+
+function hsv2hwb(args) {
+ return rgb2hwb(hsv2rgb(args))
+}
+
+function hsv2cmyk(args) {
+ return rgb2cmyk(hsv2rgb(args));
+}
+
+function hsv2keyword(args) {
+ return rgb2keyword(hsv2rgb(args));
+}
+
+// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
+function hwb2rgb(hwb) {
+ var h = hwb[0] / 360,
+ wh = hwb[1] / 100,
+ bl = hwb[2] / 100,
+ ratio = wh + bl,
+ i, v, f, n;
+
+ // wh + bl cant be > 1
+ if (ratio > 1) {
+ wh /= ratio;
+ bl /= ratio;
+ }
+
+ i = Math.floor(6 * h);
+ v = 1 - bl;
+ f = 6 * h - i;
+ if ((i & 0x01) != 0) {
+ f = 1 - f;
+ }
+ n = wh + f * (v - wh); // linear interpolation
+
+ switch (i) {
+ default:
+ case 6:
+ case 0: r = v; g = n; b = wh; break;
+ case 1: r = n; g = v; b = wh; break;
+ case 2: r = wh; g = v; b = n; break;
+ case 3: r = wh; g = n; b = v; break;
+ case 4: r = n; g = wh; b = v; break;
+ case 5: r = v; g = wh; b = n; break;
+ }
+
+ return [r * 255, g * 255, b * 255];
+}
+
+function hwb2hsl(args) {
+ return rgb2hsl(hwb2rgb(args));
+}
+
+function hwb2hsv(args) {
+ return rgb2hsv(hwb2rgb(args));
+}
+
+function hwb2cmyk(args) {
+ return rgb2cmyk(hwb2rgb(args));
+}
+
+function hwb2keyword(args) {
+ return rgb2keyword(hwb2rgb(args));
+}
+
+function cmyk2rgb(cmyk) {
+ var c = cmyk[0] / 100,
+ m = cmyk[1] / 100,
+ y = cmyk[2] / 100,
+ k = cmyk[3] / 100,
+ r, g, b;
+
+ r = 1 - Math.min(1, c * (1 - k) + k);
+ g = 1 - Math.min(1, m * (1 - k) + k);
+ b = 1 - Math.min(1, y * (1 - k) + k);
+ return [r * 255, g * 255, b * 255];
+}
+
+function cmyk2hsl(args) {
+ return rgb2hsl(cmyk2rgb(args));
+}
+
+function cmyk2hsv(args) {
+ return rgb2hsv(cmyk2rgb(args));
+}
+
+function cmyk2hwb(args) {
+ return rgb2hwb(cmyk2rgb(args));
+}
+
+function cmyk2keyword(args) {
+ return rgb2keyword(cmyk2rgb(args));
+}
+
+
+function xyz2rgb(xyz) {
+ var x = xyz[0] / 100,
+ y = xyz[1] / 100,
+ z = xyz[2] / 100,
+ r, g, b;
+
+ r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
+ g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
+ b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
+
+ // assume sRGB
+ r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
+ : r = (r * 12.92);
+
+ g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
+ : g = (g * 12.92);
+
+ b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
+ : b = (b * 12.92);
+
+ r = Math.min(Math.max(0, r), 1);
+ g = Math.min(Math.max(0, g), 1);
+ b = Math.min(Math.max(0, b), 1);
+
+ return [r * 255, g * 255, b * 255];
+}
+
+function xyz2lab(xyz) {
+ var x = xyz[0],
+ y = xyz[1],
+ z = xyz[2],
+ l, a, b;
+
+ x /= 95.047;
+ y /= 100;
+ z /= 108.883;
+
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
+
+ l = (116 * y) - 16;
+ a = 500 * (x - y);
+ b = 200 * (y - z);
+
+ return [l, a, b];
+}
+
+function xyz2lch(args) {
+ return lab2lch(xyz2lab(args));
+}
+
+function lab2xyz(lab) {
+ var l = lab[0],
+ a = lab[1],
+ b = lab[2],
+ x, y, z, y2;
+
+ if (l <= 8) {
+ y = (l * 100) / 903.3;
+ y2 = (7.787 * (y / 100)) + (16 / 116);
+ } else {
+ y = 100 * Math.pow((l + 16) / 116, 3);
+ y2 = Math.pow(y / 100, 1/3);
+ }
+
+ x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
+
+ z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
+
+ return [x, y, z];
+}
+
+function lab2lch(lab) {
+ var l = lab[0],
+ a = lab[1],
+ b = lab[2],
+ hr, h, c;
+
+ hr = Math.atan2(b, a);
+ h = hr * 360 / 2 / Math.PI;
+ if (h < 0) {
+ h += 360;
+ }
+ c = Math.sqrt(a * a + b * b);
+ return [l, c, h];
+}
+
+function lab2rgb(args) {
+ return xyz2rgb(lab2xyz(args));
+}
+
+function lch2lab(lch) {
+ var l = lch[0],
+ c = lch[1],
+ h = lch[2],
+ a, b, hr;
+
+ hr = h / 360 * 2 * Math.PI;
+ a = c * Math.cos(hr);
+ b = c * Math.sin(hr);
+ return [l, a, b];
+}
+
+function lch2xyz(args) {
+ return lab2xyz(lch2lab(args));
+}
+
+function lch2rgb(args) {
+ return lab2rgb(lch2lab(args));
+}
+
+function keyword2rgb(keyword) {
+ return cssKeywords[keyword];
+}
+
+function keyword2hsl(args) {
+ return rgb2hsl(keyword2rgb(args));
+}
+
+function keyword2hsv(args) {
+ return rgb2hsv(keyword2rgb(args));
+}
+
+function keyword2hwb(args) {
+ return rgb2hwb(keyword2rgb(args));
+}
+
+function keyword2cmyk(args) {
+ return rgb2cmyk(keyword2rgb(args));
+}
+
+function keyword2lab(args) {
+ return rgb2lab(keyword2rgb(args));
+}
+
+function keyword2xyz(args) {
+ return rgb2xyz(keyword2rgb(args));
+}
+
+var cssKeywords = {
+ aliceblue: [240,248,255],
+ antiquewhite: [250,235,215],
+ aqua: [0,255,255],
+ aquamarine: [127,255,212],
+ azure: [240,255,255],
+ beige: [245,245,220],
+ bisque: [255,228,196],
+ black: [0,0,0],
+ blanchedalmond: [255,235,205],
+ blue: [0,0,255],
+ blueviolet: [138,43,226],
+ brown: [165,42,42],
+ burlywood: [222,184,135],
+ cadetblue: [95,158,160],
+ chartreuse: [127,255,0],
+ chocolate: [210,105,30],
+ coral: [255,127,80],
+ cornflowerblue: [100,149,237],
+ cornsilk: [255,248,220],
+ crimson: [220,20,60],
+ cyan: [0,255,255],
+ darkblue: [0,0,139],
+ darkcyan: [0,139,139],
+ darkgoldenrod: [184,134,11],
+ darkgray: [169,169,169],
+ darkgreen: [0,100,0],
+ darkgrey: [169,169,169],
+ darkkhaki: [189,183,107],
+ darkmagenta: [139,0,139],
+ darkolivegreen: [85,107,47],
+ darkorange: [255,140,0],
+ darkorchid: [153,50,204],
+ darkred: [139,0,0],
+ darksalmon: [233,150,122],
+ darkseagreen: [143,188,143],
+ darkslateblue: [72,61,139],
+ darkslategray: [47,79,79],
+ darkslategrey: [47,79,79],
+ darkturquoise: [0,206,209],
+ darkviolet: [148,0,211],
+ deeppink: [255,20,147],
+ deepskyblue: [0,191,255],
+ dimgray: [105,105,105],
+ dimgrey: [105,105,105],
+ dodgerblue: [30,144,255],
+ firebrick: [178,34,34],
+ floralwhite: [255,250,240],
+ forestgreen: [34,139,34],
+ fuchsia: [255,0,255],
+ gainsboro: [220,220,220],
+ ghostwhite: [248,248,255],
+ gold: [255,215,0],
+ goldenrod: [218,165,32],
+ gray: [128,128,128],
+ green: [0,128,0],
+ greenyellow: [173,255,47],
+ grey: [128,128,128],
+ honeydew: [240,255,240],
+ hotpink: [255,105,180],
+ indianred: [205,92,92],
+ indigo: [75,0,130],
+ ivory: [255,255,240],
+ khaki: [240,230,140],
+ lavender: [230,230,250],
+ lavenderblush: [255,240,245],
+ lawngreen: [124,252,0],
+ lemonchiffon: [255,250,205],
+ lightblue: [173,216,230],
+ lightcoral: [240,128,128],
+ lightcyan: [224,255,255],
+ lightgoldenrodyellow: [250,250,210],
+ lightgray: [211,211,211],
+ lightgreen: [144,238,144],
+ lightgrey: [211,211,211],
+ lightpink: [255,182,193],
+ lightsalmon: [255,160,122],
+ lightseagreen: [32,178,170],
+ lightskyblue: [135,206,250],
+ lightslategray: [119,136,153],
+ lightslategrey: [119,136,153],
+ lightsteelblue: [176,196,222],
+ lightyellow: [255,255,224],
+ lime: [0,255,0],
+ limegreen: [50,205,50],
+ linen: [250,240,230],
+ magenta: [255,0,255],
+ maroon: [128,0,0],
+ mediumaquamarine: [102,205,170],
+ mediumblue: [0,0,205],
+ mediumorchid: [186,85,211],
+ mediumpurple: [147,112,219],
+ mediumseagreen: [60,179,113],
+ mediumslateblue: [123,104,238],
+ mediumspringgreen: [0,250,154],
+ mediumturquoise: [72,209,204],
+ mediumvioletred: [199,21,133],
+ midnightblue: [25,25,112],
+ mintcream: [245,255,250],
+ mistyrose: [255,228,225],
+ moccasin: [255,228,181],
+ navajowhite: [255,222,173],
+ navy: [0,0,128],
+ oldlace: [253,245,230],
+ olive: [128,128,0],
+ olivedrab: [107,142,35],
+ orange: [255,165,0],
+ orangered: [255,69,0],
+ orchid: [218,112,214],
+ palegoldenrod: [238,232,170],
+ palegreen: [152,251,152],
+ paleturquoise: [175,238,238],
+ palevioletred: [219,112,147],
+ papayawhip: [255,239,213],
+ peachpuff: [255,218,185],
+ peru: [205,133,63],
+ pink: [255,192,203],
+ plum: [221,160,221],
+ powderblue: [176,224,230],
+ purple: [128,0,128],
+ rebeccapurple: [102, 51, 153],
+ red: [255,0,0],
+ rosybrown: [188,143,143],
+ royalblue: [65,105,225],
+ saddlebrown: [139,69,19],
+ salmon: [250,128,114],
+ sandybrown: [244,164,96],
+ seagreen: [46,139,87],
+ seashell: [255,245,238],
+ sienna: [160,82,45],
+ silver: [192,192,192],
+ skyblue: [135,206,235],
+ slateblue: [106,90,205],
+ slategray: [112,128,144],
+ slategrey: [112,128,144],
+ snow: [255,250,250],
+ springgreen: [0,255,127],
+ steelblue: [70,130,180],
+ tan: [210,180,140],
+ teal: [0,128,128],
+ thistle: [216,191,216],
+ tomato: [255,99,71],
+ turquoise: [64,224,208],
+ violet: [238,130,238],
+ wheat: [245,222,179],
+ white: [255,255,255],
+ whitesmoke: [245,245,245],
+ yellow: [255,255,0],
+ yellowgreen: [154,205,50]
+};
+
+var reverseKeywords = {};
+for (var key in cssKeywords) {
+ reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
+}
+
+},{}],5:[function(require,module,exports){
+var conversions = require(4);
+
+var convert = function() {
+ return new Converter();
+}
+
+for (var func in conversions) {
+ // export Raw versions
+ convert[func + "Raw"] = (function(func) {
+ // accept array or plain args
+ return function(arg) {
+ if (typeof arg == "number")
+ arg = Array.prototype.slice.call(arguments);
+ return conversions[func](arg);
+ }
+ })(func);
+
+ var pair = /(\w+)2(\w+)/.exec(func),
+ from = pair[1],
+ to = pair[2];
+
+ // export rgb2hsl and ["rgb"]["hsl"]
+ convert[from] = convert[from] || {};
+
+ convert[from][to] = convert[func] = (function(func) {
+ return function(arg) {
+ if (typeof arg == "number")
+ arg = Array.prototype.slice.call(arguments);
+
+ var val = conversions[func](arg);
+ if (typeof val == "string" || val === undefined)
+ return val; // keyword
+
+ for (var i = 0; i < val.length; i++)
+ val[i] = Math.round(val[i]);
+ return val;
+ }
+ })(func);
+}
+
+
+/* Converter does lazy conversion and caching */
+var Converter = function() {
+ this.convs = {};
+};
+
+/* Either get the values for a space or
+ set the values for a space, depending on args */
+Converter.prototype.routeSpace = function(space, args) {
+ var values = args[0];
+ if (values === undefined) {
+ // color.rgb()
+ return this.getValues(space);
+ }
+ // color.rgb(10, 10, 10)
+ if (typeof values == "number") {
+ values = Array.prototype.slice.call(args);
+ }
+
+ return this.setValues(space, values);
+};
+
+/* Set the values for a space, invalidating cache */
+Converter.prototype.setValues = function(space, values) {
+ this.space = space;
+ this.convs = {};
+ this.convs[space] = values;
+ return this;
+};
+
+/* Get the values for a space. If there's already
+ a conversion for the space, fetch it, otherwise
+ compute it */
+Converter.prototype.getValues = function(space) {
+ var vals = this.convs[space];
+ if (!vals) {
+ var fspace = this.space,
+ from = this.convs[fspace];
+ vals = convert[fspace][space](from);
+
+ this.convs[space] = vals;
+ }
+ return vals;
+};
+
+["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
+ Converter.prototype[space] = function(vals) {
+ return this.routeSpace(space, arguments);
+ }
+});
+
+module.exports = convert;
+},{"4":4}],6:[function(require,module,exports){
+'use strict'
+
+module.exports = {
+ "aliceblue": [240, 248, 255],
+ "antiquewhite": [250, 235, 215],
+ "aqua": [0, 255, 255],
+ "aquamarine": [127, 255, 212],
+ "azure": [240, 255, 255],
+ "beige": [245, 245, 220],
+ "bisque": [255, 228, 196],
+ "black": [0, 0, 0],
+ "blanchedalmond": [255, 235, 205],
+ "blue": [0, 0, 255],
+ "blueviolet": [138, 43, 226],
+ "brown": [165, 42, 42],
+ "burlywood": [222, 184, 135],
+ "cadetblue": [95, 158, 160],
+ "chartreuse": [127, 255, 0],
+ "chocolate": [210, 105, 30],
+ "coral": [255, 127, 80],
+ "cornflowerblue": [100, 149, 237],
+ "cornsilk": [255, 248, 220],
+ "crimson": [220, 20, 60],
+ "cyan": [0, 255, 255],
+ "darkblue": [0, 0, 139],
+ "darkcyan": [0, 139, 139],
+ "darkgoldenrod": [184, 134, 11],
+ "darkgray": [169, 169, 169],
+ "darkgreen": [0, 100, 0],
+ "darkgrey": [169, 169, 169],
+ "darkkhaki": [189, 183, 107],
+ "darkmagenta": [139, 0, 139],
+ "darkolivegreen": [85, 107, 47],
+ "darkorange": [255, 140, 0],
+ "darkorchid": [153, 50, 204],
+ "darkred": [139, 0, 0],
+ "darksalmon": [233, 150, 122],
+ "darkseagreen": [143, 188, 143],
+ "darkslateblue": [72, 61, 139],
+ "darkslategray": [47, 79, 79],
+ "darkslategrey": [47, 79, 79],
+ "darkturquoise": [0, 206, 209],
+ "darkviolet": [148, 0, 211],
+ "deeppink": [255, 20, 147],
+ "deepskyblue": [0, 191, 255],
+ "dimgray": [105, 105, 105],
+ "dimgrey": [105, 105, 105],
+ "dodgerblue": [30, 144, 255],
+ "firebrick": [178, 34, 34],
+ "floralwhite": [255, 250, 240],
+ "forestgreen": [34, 139, 34],
+ "fuchsia": [255, 0, 255],
+ "gainsboro": [220, 220, 220],
+ "ghostwhite": [248, 248, 255],
+ "gold": [255, 215, 0],
+ "goldenrod": [218, 165, 32],
+ "gray": [128, 128, 128],
+ "green": [0, 128, 0],
+ "greenyellow": [173, 255, 47],
+ "grey": [128, 128, 128],
+ "honeydew": [240, 255, 240],
+ "hotpink": [255, 105, 180],
+ "indianred": [205, 92, 92],
+ "indigo": [75, 0, 130],
+ "ivory": [255, 255, 240],
+ "khaki": [240, 230, 140],
+ "lavender": [230, 230, 250],
+ "lavenderblush": [255, 240, 245],
+ "lawngreen": [124, 252, 0],
+ "lemonchiffon": [255, 250, 205],
+ "lightblue": [173, 216, 230],
+ "lightcoral": [240, 128, 128],
+ "lightcyan": [224, 255, 255],
+ "lightgoldenrodyellow": [250, 250, 210],
+ "lightgray": [211, 211, 211],
+ "lightgreen": [144, 238, 144],
+ "lightgrey": [211, 211, 211],
+ "lightpink": [255, 182, 193],
+ "lightsalmon": [255, 160, 122],
+ "lightseagreen": [32, 178, 170],
+ "lightskyblue": [135, 206, 250],
+ "lightslategray": [119, 136, 153],
+ "lightslategrey": [119, 136, 153],
+ "lightsteelblue": [176, 196, 222],
+ "lightyellow": [255, 255, 224],
+ "lime": [0, 255, 0],
+ "limegreen": [50, 205, 50],
+ "linen": [250, 240, 230],
+ "magenta": [255, 0, 255],
+ "maroon": [128, 0, 0],
+ "mediumaquamarine": [102, 205, 170],
+ "mediumblue": [0, 0, 205],
+ "mediumorchid": [186, 85, 211],
+ "mediumpurple": [147, 112, 219],
+ "mediumseagreen": [60, 179, 113],
+ "mediumslateblue": [123, 104, 238],
+ "mediumspringgreen": [0, 250, 154],
+ "mediumturquoise": [72, 209, 204],
+ "mediumvioletred": [199, 21, 133],
+ "midnightblue": [25, 25, 112],
+ "mintcream": [245, 255, 250],
+ "mistyrose": [255, 228, 225],
+ "moccasin": [255, 228, 181],
+ "navajowhite": [255, 222, 173],
+ "navy": [0, 0, 128],
+ "oldlace": [253, 245, 230],
+ "olive": [128, 128, 0],
+ "olivedrab": [107, 142, 35],
+ "orange": [255, 165, 0],
+ "orangered": [255, 69, 0],
+ "orchid": [218, 112, 214],
+ "palegoldenrod": [238, 232, 170],
+ "palegreen": [152, 251, 152],
+ "paleturquoise": [175, 238, 238],
+ "palevioletred": [219, 112, 147],
+ "papayawhip": [255, 239, 213],
+ "peachpuff": [255, 218, 185],
+ "peru": [205, 133, 63],
+ "pink": [255, 192, 203],
+ "plum": [221, 160, 221],
+ "powderblue": [176, 224, 230],
+ "purple": [128, 0, 128],
+ "rebeccapurple": [102, 51, 153],
+ "red": [255, 0, 0],
+ "rosybrown": [188, 143, 143],
+ "royalblue": [65, 105, 225],
+ "saddlebrown": [139, 69, 19],
+ "salmon": [250, 128, 114],
+ "sandybrown": [244, 164, 96],
+ "seagreen": [46, 139, 87],
+ "seashell": [255, 245, 238],
+ "sienna": [160, 82, 45],
+ "silver": [192, 192, 192],
+ "skyblue": [135, 206, 235],
+ "slateblue": [106, 90, 205],
+ "slategray": [112, 128, 144],
+ "slategrey": [112, 128, 144],
+ "snow": [255, 250, 250],
+ "springgreen": [0, 255, 127],
+ "steelblue": [70, 130, 180],
+ "tan": [210, 180, 140],
+ "teal": [0, 128, 128],
+ "thistle": [216, 191, 216],
+ "tomato": [255, 99, 71],
+ "turquoise": [64, 224, 208],
+ "violet": [238, 130, 238],
+ "wheat": [245, 222, 179],
+ "white": [255, 255, 255],
+ "whitesmoke": [245, 245, 245],
+ "yellow": [255, 255, 0],
+ "yellowgreen": [154, 205, 50]
+};
+
+},{}],7:[function(require,module,exports){
+/**
+ * @namespace Chart
+ */
+var Chart = require(29)();
+
+Chart.helpers = require(45);
+
+// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
+require(27)(Chart);
+
+Chart.defaults = require(25);
+Chart.Element = require(26);
+Chart.elements = require(40);
+Chart.Interaction = require(28);
+Chart.platform = require(48);
+
+require(31)(Chart);
+require(22)(Chart);
+require(23)(Chart);
+require(24)(Chart);
+require(30)(Chart);
+require(33)(Chart);
+require(32)(Chart);
+require(35)(Chart);
+
+require(54)(Chart);
+require(52)(Chart);
+require(53)(Chart);
+require(55)(Chart);
+require(56)(Chart);
+require(57)(Chart);
+
+// Controllers must be loaded after elements
+// See Chart.core.datasetController.dataElementType
+require(15)(Chart);
+require(16)(Chart);
+require(17)(Chart);
+require(18)(Chart);
+require(19)(Chart);
+require(20)(Chart);
+require(21)(Chart);
+
+require(8)(Chart);
+require(9)(Chart);
+require(10)(Chart);
+require(11)(Chart);
+require(12)(Chart);
+require(13)(Chart);
+require(14)(Chart);
+
+// Loading built-it plugins
+var plugins = [];
+
+plugins.push(
+ require(49)(Chart),
+ require(50)(Chart),
+ require(51)(Chart)
+);
+
+Chart.plugins.register(plugins);
+
+Chart.platform.initialize();
+
+module.exports = Chart;
+if (typeof window !== 'undefined') {
+ window.imagify.Chart = Chart;
+}
+
+// DEPRECATIONS
+
+/**
+ * Provided for backward compatibility, use Chart.helpers.canvas instead.
+ * @namespace Chart.canvasHelpers
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ * @private
+ */
+Chart.canvasHelpers = Chart.helpers.canvas;
+
+},{"10":10,"11":11,"12":12,"13":13,"14":14,"15":15,"16":16,"17":17,"18":18,"19":19,"20":20,"21":21,"22":22,"23":23,"24":24,"25":25,"26":26,"27":27,"28":28,"29":29,"30":30,"31":31,"32":32,"33":33,"35":35,"40":40,"45":45,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"8":8,"9":9}],8:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Bar = function(context, config) {
+ config.type = 'bar';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],9:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Bubble = function(context, config) {
+ config.type = 'bubble';
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],10:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Doughnut = function(context, config) {
+ config.type = 'doughnut';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],11:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Line = function(context, config) {
+ config.type = 'line';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],12:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.PolarArea = function(context, config) {
+ config.type = 'polarArea';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],13:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ Chart.Radar = function(context, config) {
+ config.type = 'radar';
+
+ return new Chart(context, config);
+ };
+
+};
+
+},{}],14:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+ Chart.Scatter = function(context, config) {
+ config.type = 'scatter';
+ return new Chart(context, config);
+ };
+};
+
+},{}],15:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var elements = require(40);
+var helpers = require(45);
+
+defaults._set('bar', {
+ hover: {
+ mode: 'label'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'category',
+
+ // Specific to Bar Controller
+ categoryPercentage: 0.8,
+ barPercentage: 0.9,
+
+ // offset settings
+ offset: true,
+
+ // grid line settings
+ gridLines: {
+ offsetGridLines: true
+ }
+ }],
+
+ yAxes: [{
+ type: 'linear'
+ }]
+ }
+});
+
+defaults._set('horizontalBar', {
+ hover: {
+ mode: 'index',
+ axis: 'y'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'linear',
+ position: 'bottom'
+ }],
+
+ yAxes: [{
+ position: 'left',
+ type: 'category',
+
+ // Specific to Horizontal Bar Controller
+ categoryPercentage: 0.8,
+ barPercentage: 0.9,
+
+ // offset settings
+ offset: true,
+
+ // grid line settings
+ gridLines: {
+ offsetGridLines: true
+ }
+ }]
+ },
+
+ elements: {
+ rectangle: {
+ borderSkipped: 'left'
+ }
+ },
+
+ tooltips: {
+ callbacks: {
+ title: function(item, data) {
+ // Pick first xLabel for now
+ var title = '';
+
+ if (item.length > 0) {
+ if (item[0].yLabel) {
+ title = item[0].yLabel;
+ } else if (data.labels.length > 0 && item[0].index < data.labels.length) {
+ title = data.labels[item[0].index];
+ }
+ }
+
+ return title;
+ },
+
+ label: function(item, data) {
+ var datasetLabel = data.datasets[item.datasetIndex].label || '';
+ return datasetLabel + ': ' + item.xLabel;
+ }
+ },
+ mode: 'index',
+ axis: 'y'
+ }
+});
+
+module.exports = function(Chart) {
+
+ Chart.controllers.bar = Chart.DatasetController.extend({
+
+ dataElementType: elements.Rectangle,
+
+ initialize: function() {
+ var me = this;
+ var meta;
+
+ Chart.DatasetController.prototype.initialize.apply(me, arguments);
+
+ meta = me.getMeta();
+ meta.stack = me.getDataset().stack;
+ meta.bar = true;
+ },
+
+ update: function(reset) {
+ var me = this;
+ var rects = me.getMeta().data;
+ var i, ilen;
+
+ me._ruler = me.getRuler();
+
+ for (i = 0, ilen = rects.length; i < ilen; ++i) {
+ me.updateElement(rects[i], i, reset);
+ }
+ },
+
+ updateElement: function(rectangle, index, reset) {
+ var me = this;
+ var chart = me.chart;
+ var meta = me.getMeta();
+ var dataset = me.getDataset();
+ var custom = rectangle.custom || {};
+ var rectangleOptions = chart.options.elements.rectangle;
+
+ rectangle._xScale = me.getScaleForId(meta.xAxisID);
+ rectangle._yScale = me.getScaleForId(meta.yAxisID);
+ rectangle._datasetIndex = me.index;
+ rectangle._index = index;
+
+ rectangle._model = {
+ datasetLabel: dataset.label,
+ label: chart.data.labels[index],
+ borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped,
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor),
+ borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth)
+ };
+
+ me.updateElementGeometry(rectangle, index, reset);
+
+ rectangle.pivot();
+ },
+
+ /**
+ * @private
+ */
+ updateElementGeometry: function(rectangle, index, reset) {
+ var me = this;
+ var model = rectangle._model;
+ var vscale = me.getValueScale();
+ var base = vscale.getBasePixel();
+ var horizontal = vscale.isHorizontal();
+ var ruler = me._ruler || me.getRuler();
+ var vpixels = me.calculateBarValuePixels(me.index, index);
+ var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
+
+ model.horizontal = horizontal;
+ model.base = reset ? base : vpixels.base;
+ model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
+ model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
+ model.height = horizontal ? ipixels.size : undefined;
+ model.width = horizontal ? undefined : ipixels.size;
+ },
+
+ /**
+ * @private
+ */
+ getValueScaleId: function() {
+ return this.getMeta().yAxisID;
+ },
+
+ /**
+ * @private
+ */
+ getIndexScaleId: function() {
+ return this.getMeta().xAxisID;
+ },
+
+ /**
+ * @private
+ */
+ getValueScale: function() {
+ return this.getScaleForId(this.getValueScaleId());
+ },
+
+ /**
+ * @private
+ */
+ getIndexScale: function() {
+ return this.getScaleForId(this.getIndexScaleId());
+ },
+
+ /**
+ * Returns the effective number of stacks based on groups and bar visibility.
+ * @private
+ */
+ getStackCount: function(last) {
+ var me = this;
+ var chart = me.chart;
+ var scale = me.getIndexScale();
+ var stacked = scale.options.stacked;
+ var ilen = last === undefined ? chart.data.datasets.length : last + 1;
+ var stacks = [];
+ var i, meta;
+
+ for (i = 0; i < ilen; ++i) {
+ meta = chart.getDatasetMeta(i);
+ if (meta.bar && chart.isDatasetVisible(i) &&
+ (stacked === false ||
+ (stacked === true && stacks.indexOf(meta.stack) === -1) ||
+ (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
+ stacks.push(meta.stack);
+ }
+ }
+
+ return stacks.length;
+ },
+
+ /**
+ * Returns the stack index for the given dataset based on groups and bar visibility.
+ * @private
+ */
+ getStackIndex: function(datasetIndex) {
+ return this.getStackCount(datasetIndex) - 1;
+ },
+
+ /**
+ * @private
+ */
+ getRuler: function() {
+ var me = this;
+ var scale = me.getIndexScale();
+ var stackCount = me.getStackCount();
+ var datasetIndex = me.index;
+ var pixels = [];
+ var isHorizontal = scale.isHorizontal();
+ var start = isHorizontal ? scale.left : scale.top;
+ var end = start + (isHorizontal ? scale.width : scale.height);
+ var i, ilen;
+
+ for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
+ pixels.push(scale.getPixelForValue(null, i, datasetIndex));
+ }
+
+ return {
+ pixels: pixels,
+ start: start,
+ end: end,
+ stackCount: stackCount,
+ scale: scale
+ };
+ },
+
+ /**
+ * Note: pixel values are not clamped to the scale area.
+ * @private
+ */
+ calculateBarValuePixels: function(datasetIndex, index) {
+ var me = this;
+ var chart = me.chart;
+ var meta = me.getMeta();
+ var scale = me.getValueScale();
+ var datasets = chart.data.datasets;
+ var value = scale.getRightValue(datasets[datasetIndex].data[index]);
+ var stacked = scale.options.stacked;
+ var stack = meta.stack;
+ var start = 0;
+ var i, imeta, ivalue, base, head, size;
+
+ if (stacked || (stacked === undefined && stack !== undefined)) {
+ for (i = 0; i < datasetIndex; ++i) {
+ imeta = chart.getDatasetMeta(i);
+
+ if (imeta.bar &&
+ imeta.stack === stack &&
+ imeta.controller.getValueScaleId() === scale.id &&
+ chart.isDatasetVisible(i)) {
+
+ ivalue = scale.getRightValue(datasets[i].data[index]);
+ if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
+ start += ivalue;
+ }
+ }
+ }
+ }
+
+ base = scale.getPixelForValue(start);
+ head = scale.getPixelForValue(start + value);
+ size = (head - base) / 2;
+
+ return {
+ size: size,
+ base: base,
+ head: head,
+ center: head + size / 2
+ };
+ },
+
+ /**
+ * @private
+ */
+ calculateBarIndexPixels: function(datasetIndex, index, ruler) {
+ var me = this;
+ var options = ruler.scale.options;
+ var stackIndex = me.getStackIndex(datasetIndex);
+ var pixels = ruler.pixels;
+ var base = pixels[index];
+ var length = pixels.length;
+ var start = ruler.start;
+ var end = ruler.end;
+ var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;
+
+ if (length === 1) {
+ leftSampleSize = base > start ? base - start : end - base;
+ rightSampleSize = base < end ? end - base : base - start;
+ } else {
+ if (index > 0) {
+ leftSampleSize = (base - pixels[index - 1]) / 2;
+ if (index === length - 1) {
+ rightSampleSize = leftSampleSize;
+ }
+ }
+ if (index < length - 1) {
+ rightSampleSize = (pixels[index + 1] - base) / 2;
+ if (index === 0) {
+ leftSampleSize = rightSampleSize;
+ }
+ }
+ }
+
+ leftCategorySize = leftSampleSize * options.categoryPercentage;
+ rightCategorySize = rightSampleSize * options.categoryPercentage;
+ fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
+ size = fullBarSize * options.barPercentage;
+
+ size = Math.min(
+ helpers.valueOrDefault(options.barThickness, size),
+ helpers.valueOrDefault(options.maxBarThickness, Infinity));
+
+ base -= leftCategorySize;
+ base += fullBarSize * stackIndex;
+ base += (fullBarSize - size) / 2;
+
+ return {
+ size: size,
+ base: base,
+ head: base + size,
+ center: base + size / 2
+ };
+ },
+
+ draw: function() {
+ var me = this;
+ var chart = me.chart;
+ var scale = me.getValueScale();
+ var rects = me.getMeta().data;
+ var dataset = me.getDataset();
+ var ilen = rects.length;
+ var i = 0;
+
+ helpers.canvas.clipArea(chart.ctx, chart.chartArea);
+
+ for (; i < ilen; ++i) {
+ if (!isNaN(scale.getRightValue(dataset.data[i]))) {
+ rects[i].draw();
+ }
+ }
+
+ helpers.canvas.unclipArea(chart.ctx);
+ },
+
+ setHoverStyle: function(rectangle) {
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
+ var index = rectangle._index;
+ var custom = rectangle.custom || {};
+ var model = rectangle._model;
+
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor));
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
+ },
+
+ removeHoverStyle: function(rectangle) {
+ var dataset = this.chart.data.datasets[rectangle._datasetIndex];
+ var index = rectangle._index;
+ var custom = rectangle.custom || {};
+ var model = rectangle._model;
+ var rectangleElementOptions = this.chart.options.elements.rectangle;
+
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor);
+ model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor);
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth);
+ }
+ });
+
+ Chart.controllers.horizontalBar = Chart.controllers.bar.extend({
+ /**
+ * @private
+ */
+ getValueScaleId: function() {
+ return this.getMeta().xAxisID;
+ },
+
+ /**
+ * @private
+ */
+ getIndexScaleId: function() {
+ return this.getMeta().yAxisID;
+ }
+ });
+};
+
+},{"25":25,"40":40,"45":45}],16:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var elements = require(40);
+var helpers = require(45);
+
+defaults._set('bubble', {
+ hover: {
+ mode: 'single'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'linear', // bubble should probably use a linear scale by default
+ position: 'bottom',
+ id: 'x-axis-0' // need an ID so datasets can reference the scale
+ }],
+ yAxes: [{
+ type: 'linear',
+ position: 'left',
+ id: 'y-axis-0'
+ }]
+ },
+
+ tooltips: {
+ callbacks: {
+ title: function() {
+ // Title doesn't make sense for scatter since we format the data as a point
+ return '';
+ },
+ label: function(item, data) {
+ var datasetLabel = data.datasets[item.datasetIndex].label || '';
+ var dataPoint = data.datasets[item.datasetIndex].data[item.index];
+ return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
+ }
+ }
+ }
+});
+
+
+module.exports = function(Chart) {
+
+ Chart.controllers.bubble = Chart.DatasetController.extend({
+ /**
+ * @protected
+ */
+ dataElementType: elements.Point,
+
+ /**
+ * @protected
+ */
+ update: function(reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var points = meta.data;
+
+ // Update Points
+ helpers.each(points, function(point, index) {
+ me.updateElement(point, index, reset);
+ });
+ },
+
+ /**
+ * @protected
+ */
+ updateElement: function(point, index, reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var custom = point.custom || {};
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var options = me._resolveElementOptions(point, index);
+ var data = me.getDataset().data[index];
+ var dsIndex = me.index;
+
+ var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
+ var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
+
+ point._xScale = xScale;
+ point._yScale = yScale;
+ point._options = options;
+ point._datasetIndex = dsIndex;
+ point._index = index;
+ point._model = {
+ backgroundColor: options.backgroundColor,
+ borderColor: options.borderColor,
+ borderWidth: options.borderWidth,
+ hitRadius: options.hitRadius,
+ pointStyle: options.pointStyle,
+ radius: reset ? 0 : options.radius,
+ skip: custom.skip || isNaN(x) || isNaN(y),
+ x: x,
+ y: y,
+ };
+
+ point.pivot();
+ },
+
+ /**
+ * @protected
+ */
+ setHoverStyle: function(point) {
+ var model = point._model;
+ var options = point._options;
+
+ model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor));
+ model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor));
+ model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth);
+ model.radius = options.radius + options.hoverRadius;
+ },
+
+ /**
+ * @protected
+ */
+ removeHoverStyle: function(point) {
+ var model = point._model;
+ var options = point._options;
+
+ model.backgroundColor = options.backgroundColor;
+ model.borderColor = options.borderColor;
+ model.borderWidth = options.borderWidth;
+ model.radius = options.radius;
+ },
+
+ /**
+ * @private
+ */
+ _resolveElementOptions: function(point, index) {
+ var me = this;
+ var chart = me.chart;
+ var datasets = chart.data.datasets;
+ var dataset = datasets[me.index];
+ var custom = point.custom || {};
+ var options = chart.options.elements.point;
+ var resolve = helpers.options.resolve;
+ var data = dataset.data[index];
+ var values = {};
+ var i, ilen, key;
+
+ // Scriptable options
+ var context = {
+ chart: chart,
+ dataIndex: index,
+ dataset: dataset,
+ datasetIndex: me.index
+ };
+
+ var keys = [
+ 'backgroundColor',
+ 'borderColor',
+ 'borderWidth',
+ 'hoverBackgroundColor',
+ 'hoverBorderColor',
+ 'hoverBorderWidth',
+ 'hoverRadius',
+ 'hitRadius',
+ 'pointStyle'
+ ];
+
+ for (i = 0, ilen = keys.length; i < ilen; ++i) {
+ key = keys[i];
+ values[key] = resolve([
+ custom[key],
+ dataset[key],
+ options[key]
+ ], context, index);
+ }
+
+ // Custom radius resolution
+ values.radius = resolve([
+ custom.radius,
+ data ? data.r : undefined,
+ dataset.radius,
+ options.radius
+ ], context, index);
+
+ return values;
+ }
+ });
+};
+
+},{"25":25,"40":40,"45":45}],17:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var elements = require(40);
+var helpers = require(45);
+
+defaults._set('doughnut', {
+ animation: {
+ // Boolean - Whether we animate the rotation of the Doughnut
+ animateRotate: true,
+ // Boolean - Whether we animate scaling the Doughnut from the centre
+ animateScale: false
+ },
+ hover: {
+ mode: 'single'
+ },
+ legendCallback: function(chart) {
+ var text = [];
+ text.push('');
+
+ var data = chart.data;
+ var datasets = data.datasets;
+ var labels = data.labels;
+
+ if (datasets.length) {
+ for (var i = 0; i < datasets[0].data.length; ++i) {
+ text.push(' ');
+ if (labels[i]) {
+ text.push(labels[i]);
+ }
+ text.push(' ');
+ }
+ }
+
+ text.push(' ');
+ return text.join('');
+ },
+ legend: {
+ labels: {
+ generateLabels: function(chart) {
+ var data = chart.data;
+ if (data.labels.length && data.datasets.length) {
+ return data.labels.map(function(label, i) {
+ var meta = chart.getDatasetMeta(0);
+ var ds = data.datasets[0];
+ var arc = meta.data[i];
+ var custom = arc && arc.custom || {};
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
+ var arcOpts = chart.options.elements.arc;
+ var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
+ var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
+ var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
+
+ return {
+ text: label,
+ fillStyle: fill,
+ strokeStyle: stroke,
+ lineWidth: bw,
+ hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
+
+ // Extra data used for toggling the correct item
+ index: i
+ };
+ });
+ }
+ return [];
+ }
+ },
+
+ onClick: function(e, legendItem) {
+ var index = legendItem.index;
+ var chart = this.chart;
+ var i, ilen, meta;
+
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
+ meta = chart.getDatasetMeta(i);
+ // toggle visibility of index if exists
+ if (meta.data[index]) {
+ meta.data[index].hidden = !meta.data[index].hidden;
+ }
+ }
+
+ chart.update();
+ }
+ },
+
+ // The percentage of the chart that we cut out of the middle.
+ cutoutPercentage: 50,
+
+ // The rotation of the chart, where the first data arc begins.
+ rotation: Math.PI * -0.5,
+
+ // The total circumference of the chart.
+ circumference: Math.PI * 2.0,
+
+ // Need to override these to give a nice default
+ tooltips: {
+ callbacks: {
+ title: function() {
+ return '';
+ },
+ label: function(tooltipItem, data) {
+ var dataLabel = data.labels[tooltipItem.index];
+ var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
+
+ if (helpers.isArray(dataLabel)) {
+ // show value on first line of multiline label
+ // need to clone because we are changing the value
+ dataLabel = dataLabel.slice();
+ dataLabel[0] += value;
+ } else {
+ dataLabel += value;
+ }
+
+ return dataLabel;
+ }
+ }
+ }
+});
+
+defaults._set('pie', helpers.clone(defaults.doughnut));
+defaults._set('pie', {
+ cutoutPercentage: 0
+});
+
+module.exports = function(Chart) {
+
+ Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({
+
+ dataElementType: elements.Arc,
+
+ linkScales: helpers.noop,
+
+ // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
+ getRingIndex: function(datasetIndex) {
+ var ringIndex = 0;
+
+ for (var j = 0; j < datasetIndex; ++j) {
+ if (this.chart.isDatasetVisible(j)) {
+ ++ringIndex;
+ }
+ }
+
+ return ringIndex;
+ },
+
+ update: function(reset) {
+ var me = this;
+ var chart = me.chart;
+ var chartArea = chart.chartArea;
+ var opts = chart.options;
+ var arcOpts = opts.elements.arc;
+ var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth;
+ var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth;
+ var minSize = Math.min(availableWidth, availableHeight);
+ var offset = {x: 0, y: 0};
+ var meta = me.getMeta();
+ var cutoutPercentage = opts.cutoutPercentage;
+ var circumference = opts.circumference;
+
+ // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
+ if (circumference < Math.PI * 2.0) {
+ var startAngle = opts.rotation % (Math.PI * 2.0);
+ startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
+ var endAngle = startAngle + circumference;
+ var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
+ var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
+ var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
+ var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
+ var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
+ var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
+ var cutout = cutoutPercentage / 100.0;
+ var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
+ var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
+ var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
+ minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
+ offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
+ }
+
+ chart.borderWidth = me.getMaxBorderWidth(meta.data);
+ chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
+ chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
+ chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
+ chart.offsetX = offset.x * chart.outerRadius;
+ chart.offsetY = offset.y * chart.outerRadius;
+
+ meta.total = me.calculateTotal();
+
+ me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index));
+ me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0);
+
+ helpers.each(meta.data, function(arc, index) {
+ me.updateElement(arc, index, reset);
+ });
+ },
+
+ updateElement: function(arc, index, reset) {
+ var me = this;
+ var chart = me.chart;
+ var chartArea = chart.chartArea;
+ var opts = chart.options;
+ var animationOpts = opts.animation;
+ var centerX = (chartArea.left + chartArea.right) / 2;
+ var centerY = (chartArea.top + chartArea.bottom) / 2;
+ var startAngle = opts.rotation; // non reset case handled later
+ var endAngle = opts.rotation; // non reset case handled later
+ var dataset = me.getDataset();
+ var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI));
+ var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
+ var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
+
+ helpers.extend(arc, {
+ // Utility
+ _datasetIndex: me.index,
+ _index: index,
+
+ // Desired view properties
+ _model: {
+ x: centerX + chart.offsetX,
+ y: centerY + chart.offsetY,
+ startAngle: startAngle,
+ endAngle: endAngle,
+ circumference: circumference,
+ outerRadius: outerRadius,
+ innerRadius: innerRadius,
+ label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
+ }
+ });
+
+ var model = arc._model;
+ // Resets the visual styles
+ this.removeHoverStyle(arc);
+
+ // Set correct angles if not resetting
+ if (!reset || !animationOpts.animateRotate) {
+ if (index === 0) {
+ model.startAngle = opts.rotation;
+ } else {
+ model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
+ }
+
+ model.endAngle = model.startAngle + model.circumference;
+ }
+
+ arc.pivot();
+ },
+
+ removeHoverStyle: function(arc) {
+ Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
+ },
+
+ calculateTotal: function() {
+ var dataset = this.getDataset();
+ var meta = this.getMeta();
+ var total = 0;
+ var value;
+
+ helpers.each(meta.data, function(element, index) {
+ value = dataset.data[index];
+ if (!isNaN(value) && !element.hidden) {
+ total += Math.abs(value);
+ }
+ });
+
+ /* if (total === 0) {
+ total = NaN;
+ }*/
+
+ return total;
+ },
+
+ calculateCircumference: function(value) {
+ var total = this.getMeta().total;
+ if (total > 0 && !isNaN(value)) {
+ return (Math.PI * 2.0) * (value / total);
+ }
+ return 0;
+ },
+
+ // gets the max border or hover width to properly scale pie charts
+ getMaxBorderWidth: function(arcs) {
+ var max = 0;
+ var index = this.index;
+ var length = arcs.length;
+ var borderWidth;
+ var hoverWidth;
+
+ for (var i = 0; i < length; i++) {
+ borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0;
+ hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0;
+
+ max = borderWidth > max ? borderWidth : max;
+ max = hoverWidth > max ? hoverWidth : max;
+ }
+ return max;
+ }
+ });
+};
+
+},{"25":25,"40":40,"45":45}],18:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var elements = require(40);
+var helpers = require(45);
+
+defaults._set('line', {
+ showLines: true,
+ spanGaps: false,
+
+ hover: {
+ mode: 'label'
+ },
+
+ scales: {
+ xAxes: [{
+ type: 'category',
+ id: 'x-axis-0'
+ }],
+ yAxes: [{
+ type: 'linear',
+ id: 'y-axis-0'
+ }]
+ }
+});
+
+module.exports = function(Chart) {
+
+ function lineEnabled(dataset, options) {
+ return helpers.valueOrDefault(dataset.showLine, options.showLines);
+ }
+
+ Chart.controllers.line = Chart.DatasetController.extend({
+
+ datasetElementType: elements.Line,
+
+ dataElementType: elements.Point,
+
+ update: function(reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var line = meta.dataset;
+ var points = meta.data || [];
+ var options = me.chart.options;
+ var lineElementOptions = options.elements.line;
+ var scale = me.getScaleForId(meta.yAxisID);
+ var i, ilen, custom;
+ var dataset = me.getDataset();
+ var showLine = lineEnabled(dataset, options);
+
+ // Update Line
+ if (showLine) {
+ custom = line.custom || {};
+
+ // Compatibility: If the properties are defined with only the old name, use those values
+ if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
+ dataset.lineTension = dataset.tension;
+ }
+
+ // Utility
+ line._scale = scale;
+ line._datasetIndex = me.index;
+ // Data
+ line._children = points;
+ // Model
+ line._model = {
+ // Appearance
+ // The default behavior of lines is to break at null values, according
+ // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
+ // This option gives lines the ability to span gaps
+ spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps,
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
+ borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
+ borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
+ borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
+ borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
+ borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
+ fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
+ steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped),
+ cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode),
+ };
+
+ line.pivot();
+ }
+
+ // Update Points
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
+ me.updateElement(points[i], i, reset);
+ }
+
+ if (showLine && line._model.tension !== 0) {
+ me.updateBezierControlPoints();
+ }
+
+ // Now pivot the point for animation
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
+ points[i].pivot();
+ }
+ },
+
+ getPointBackgroundColor: function(point, index) {
+ var backgroundColor = this.chart.options.elements.point.backgroundColor;
+ var dataset = this.getDataset();
+ var custom = point.custom || {};
+
+ if (custom.backgroundColor) {
+ backgroundColor = custom.backgroundColor;
+ } else if (dataset.pointBackgroundColor) {
+ backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor);
+ } else if (dataset.backgroundColor) {
+ backgroundColor = dataset.backgroundColor;
+ }
+
+ return backgroundColor;
+ },
+
+ getPointBorderColor: function(point, index) {
+ var borderColor = this.chart.options.elements.point.borderColor;
+ var dataset = this.getDataset();
+ var custom = point.custom || {};
+
+ if (custom.borderColor) {
+ borderColor = custom.borderColor;
+ } else if (dataset.pointBorderColor) {
+ borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor);
+ } else if (dataset.borderColor) {
+ borderColor = dataset.borderColor;
+ }
+
+ return borderColor;
+ },
+
+ getPointBorderWidth: function(point, index) {
+ var borderWidth = this.chart.options.elements.point.borderWidth;
+ var dataset = this.getDataset();
+ var custom = point.custom || {};
+
+ if (!isNaN(custom.borderWidth)) {
+ borderWidth = custom.borderWidth;
+ } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) {
+ borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth);
+ } else if (!isNaN(dataset.borderWidth)) {
+ borderWidth = dataset.borderWidth;
+ }
+
+ return borderWidth;
+ },
+
+ updateElement: function(point, index, reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var custom = point.custom || {};
+ var dataset = me.getDataset();
+ var datasetIndex = me.index;
+ var value = dataset.data[index];
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var xScale = me.getScaleForId(meta.xAxisID);
+ var pointOptions = me.chart.options.elements.point;
+ var x, y;
+
+ // Compatibility: If the properties are defined with only the old name, use those values
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
+ dataset.pointRadius = dataset.radius;
+ }
+ if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
+ dataset.pointHitRadius = dataset.hitRadius;
+ }
+
+ x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
+ y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
+
+ // Utility
+ point._xScale = xScale;
+ point._yScale = yScale;
+ point._datasetIndex = datasetIndex;
+ point._index = index;
+
+ // Desired view properties
+ point._model = {
+ x: x,
+ y: y,
+ skip: custom.skip || isNaN(x) || isNaN(y),
+ // Appearance
+ radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius),
+ pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle),
+ backgroundColor: me.getPointBackgroundColor(point, index),
+ borderColor: me.getPointBorderColor(point, index),
+ borderWidth: me.getPointBorderWidth(point, index),
+ tension: meta.dataset._model ? meta.dataset._model.tension : 0,
+ steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false,
+ // Tooltip
+ hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius)
+ };
+ },
+
+ calculatePointY: function(value, index, datasetIndex) {
+ var me = this;
+ var chart = me.chart;
+ var meta = me.getMeta();
+ var yScale = me.getScaleForId(meta.yAxisID);
+ var sumPos = 0;
+ var sumNeg = 0;
+ var i, ds, dsMeta;
+
+ if (yScale.options.stacked) {
+ for (i = 0; i < datasetIndex; i++) {
+ ds = chart.data.datasets[i];
+ dsMeta = chart.getDatasetMeta(i);
+ if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
+ var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
+ if (stackedRightValue < 0) {
+ sumNeg += stackedRightValue || 0;
+ } else {
+ sumPos += stackedRightValue || 0;
+ }
+ }
+ }
+
+ var rightValue = Number(yScale.getRightValue(value));
+ if (rightValue < 0) {
+ return yScale.getPixelForValue(sumNeg + rightValue);
+ }
+ return yScale.getPixelForValue(sumPos + rightValue);
+ }
+
+ return yScale.getPixelForValue(value);
+ },
+
+ updateBezierControlPoints: function() {
+ var me = this;
+ var meta = me.getMeta();
+ var area = me.chart.chartArea;
+ var points = (meta.data || []);
+ var i, ilen, point, model, controlPoints;
+
+ // Only consider points that are drawn in case the spanGaps option is used
+ if (meta.dataset._model.spanGaps) {
+ points = points.filter(function(pt) {
+ return !pt._model.skip;
+ });
+ }
+
+ function capControlPoint(pt, min, max) {
+ return Math.max(Math.min(pt, max), min);
+ }
+
+ if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
+ helpers.splineCurveMonotone(points);
+ } else {
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
+ point = points[i];
+ model = point._model;
+ controlPoints = helpers.splineCurve(
+ helpers.previousItem(points, i)._model,
+ model,
+ helpers.nextItem(points, i)._model,
+ meta.dataset._model.tension
+ );
+ model.controlPointPreviousX = controlPoints.previous.x;
+ model.controlPointPreviousY = controlPoints.previous.y;
+ model.controlPointNextX = controlPoints.next.x;
+ model.controlPointNextY = controlPoints.next.y;
+ }
+ }
+
+ if (me.chart.options.elements.line.capBezierPoints) {
+ for (i = 0, ilen = points.length; i < ilen; ++i) {
+ model = points[i]._model;
+ model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
+ model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
+ model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
+ model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
+ }
+ }
+ },
+
+ draw: function() {
+ var me = this;
+ var chart = me.chart;
+ var meta = me.getMeta();
+ var points = meta.data || [];
+ var area = chart.chartArea;
+ var ilen = points.length;
+ var i = 0;
+
+ helpers.canvas.clipArea(chart.ctx, area);
+
+ if (lineEnabled(me.getDataset(), chart.options)) {
+ meta.dataset.draw();
+ }
+
+ helpers.canvas.unclipArea(chart.ctx);
+
+ // Draw the points
+ for (; i < ilen; ++i) {
+ points[i].draw(area);
+ }
+ },
+
+ setHoverStyle: function(point) {
+ // Point
+ var dataset = this.chart.data.datasets[point._datasetIndex];
+ var index = point._index;
+ var custom = point.custom || {};
+ var model = point._model;
+
+ model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
+ model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
+ model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
+ model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
+ },
+
+ removeHoverStyle: function(point) {
+ var me = this;
+ var dataset = me.chart.data.datasets[point._datasetIndex];
+ var index = point._index;
+ var custom = point.custom || {};
+ var model = point._model;
+
+ // Compatibility: If the properties are defined with only the old name, use those values
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
+ dataset.pointRadius = dataset.radius;
+ }
+
+ model.radius = custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, me.chart.options.elements.point.radius);
+ model.backgroundColor = me.getPointBackgroundColor(point, index);
+ model.borderColor = me.getPointBorderColor(point, index);
+ model.borderWidth = me.getPointBorderWidth(point, index);
+ }
+ });
+};
+
+},{"25":25,"40":40,"45":45}],19:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var elements = require(40);
+var helpers = require(45);
+
+defaults._set('polarArea', {
+ scale: {
+ type: 'radialLinear',
+ angleLines: {
+ display: false
+ },
+ gridLines: {
+ circular: true
+ },
+ pointLabels: {
+ display: false
+ },
+ ticks: {
+ beginAtZero: true
+ }
+ },
+
+ // Boolean - Whether to animate the rotation of the chart
+ animation: {
+ animateRotate: true,
+ animateScale: true
+ },
+
+ startAngle: -0.5 * Math.PI,
+ legendCallback: function(chart) {
+ var text = [];
+ text.push('');
+
+ var data = chart.data;
+ var datasets = data.datasets;
+ var labels = data.labels;
+
+ if (datasets.length) {
+ for (var i = 0; i < datasets[0].data.length; ++i) {
+ text.push(' ');
+ if (labels[i]) {
+ text.push(labels[i]);
+ }
+ text.push(' ');
+ }
+ }
+
+ text.push(' ');
+ return text.join('');
+ },
+ legend: {
+ labels: {
+ generateLabels: function(chart) {
+ var data = chart.data;
+ if (data.labels.length && data.datasets.length) {
+ return data.labels.map(function(label, i) {
+ var meta = chart.getDatasetMeta(0);
+ var ds = data.datasets[0];
+ var arc = meta.data[i];
+ var custom = arc.custom || {};
+ var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
+ var arcOpts = chart.options.elements.arc;
+ var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
+ var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
+ var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
+
+ return {
+ text: label,
+ fillStyle: fill,
+ strokeStyle: stroke,
+ lineWidth: bw,
+ hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
+
+ // Extra data used for toggling the correct item
+ index: i
+ };
+ });
+ }
+ return [];
+ }
+ },
+
+ onClick: function(e, legendItem) {
+ var index = legendItem.index;
+ var chart = this.chart;
+ var i, ilen, meta;
+
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
+ meta = chart.getDatasetMeta(i);
+ meta.data[index].hidden = !meta.data[index].hidden;
+ }
+
+ chart.update();
+ }
+ },
+
+ // Need to override these to give a nice default
+ tooltips: {
+ callbacks: {
+ title: function() {
+ return '';
+ },
+ label: function(item, data) {
+ return data.labels[item.index] + ': ' + item.yLabel;
+ }
+ }
+ }
+});
+
+module.exports = function(Chart) {
+
+ Chart.controllers.polarArea = Chart.DatasetController.extend({
+
+ dataElementType: elements.Arc,
+
+ linkScales: helpers.noop,
+
+ update: function(reset) {
+ var me = this;
+ var chart = me.chart;
+ var chartArea = chart.chartArea;
+ var meta = me.getMeta();
+ var opts = chart.options;
+ var arcOpts = opts.elements.arc;
+ var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
+ chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0);
+ chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
+ chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
+
+ me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
+ me.innerRadius = me.outerRadius - chart.radiusLength;
+
+ meta.count = me.countVisibleElements();
+
+ helpers.each(meta.data, function(arc, index) {
+ me.updateElement(arc, index, reset);
+ });
+ },
+
+ updateElement: function(arc, index, reset) {
+ var me = this;
+ var chart = me.chart;
+ var dataset = me.getDataset();
+ var opts = chart.options;
+ var animationOpts = opts.animation;
+ var scale = chart.scale;
+ var labels = chart.data.labels;
+
+ var circumference = me.calculateCircumference(dataset.data[index]);
+ var centerX = scale.xCenter;
+ var centerY = scale.yCenter;
+
+ // If there is NaN data before us, we need to calculate the starting angle correctly.
+ // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data
+ var visibleCount = 0;
+ var meta = me.getMeta();
+ for (var i = 0; i < index; ++i) {
+ if (!isNaN(dataset.data[i]) && !meta.data[i].hidden) {
+ ++visibleCount;
+ }
+ }
+
+ // var negHalfPI = -0.5 * Math.PI;
+ var datasetStartAngle = opts.startAngle;
+ var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
+ var startAngle = datasetStartAngle + (circumference * visibleCount);
+ var endAngle = startAngle + (arc.hidden ? 0 : circumference);
+
+ var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
+
+ helpers.extend(arc, {
+ // Utility
+ _datasetIndex: me.index,
+ _index: index,
+ _scale: scale,
+
+ // Desired view properties
+ _model: {
+ x: centerX,
+ y: centerY,
+ innerRadius: 0,
+ outerRadius: reset ? resetRadius : distance,
+ startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
+ endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
+ label: helpers.valueAtIndexOrDefault(labels, index, labels[index])
+ }
+ });
+
+ // Apply border and fill style
+ me.removeHoverStyle(arc);
+
+ arc.pivot();
+ },
+
+ removeHoverStyle: function(arc) {
+ Chart.DatasetController.prototype.removeHoverStyle.call(this, arc, this.chart.options.elements.arc);
+ },
+
+ countVisibleElements: function() {
+ var dataset = this.getDataset();
+ var meta = this.getMeta();
+ var count = 0;
+
+ helpers.each(meta.data, function(element, index) {
+ if (!isNaN(dataset.data[index]) && !element.hidden) {
+ count++;
+ }
+ });
+
+ return count;
+ },
+
+ calculateCircumference: function(value) {
+ var count = this.getMeta().count;
+ if (count > 0 && !isNaN(value)) {
+ return (2 * Math.PI) / count;
+ }
+ return 0;
+ }
+ });
+};
+
+},{"25":25,"40":40,"45":45}],20:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var elements = require(40);
+var helpers = require(45);
+
+defaults._set('radar', {
+ scale: {
+ type: 'radialLinear'
+ },
+ elements: {
+ line: {
+ tension: 0 // no bezier in radar
+ }
+ }
+});
+
+module.exports = function(Chart) {
+
+ Chart.controllers.radar = Chart.DatasetController.extend({
+
+ datasetElementType: elements.Line,
+
+ dataElementType: elements.Point,
+
+ linkScales: helpers.noop,
+
+ update: function(reset) {
+ var me = this;
+ var meta = me.getMeta();
+ var line = meta.dataset;
+ var points = meta.data;
+ var custom = line.custom || {};
+ var dataset = me.getDataset();
+ var lineElementOptions = me.chart.options.elements.line;
+ var scale = me.chart.scale;
+
+ // Compatibility: If the properties are defined with only the old name, use those values
+ if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
+ dataset.lineTension = dataset.tension;
+ }
+
+ helpers.extend(meta.dataset, {
+ // Utility
+ _datasetIndex: me.index,
+ _scale: scale,
+ // Data
+ _children: points,
+ _loop: true,
+ // Model
+ _model: {
+ // Appearance
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension),
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth),
+ borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor),
+ fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill),
+ borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle),
+ borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash),
+ borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset),
+ borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle),
+ }
+ });
+
+ meta.dataset.pivot();
+
+ // Update Points
+ helpers.each(points, function(point, index) {
+ me.updateElement(point, index, reset);
+ }, me);
+
+ // Update bezier control points
+ me.updateBezierControlPoints();
+ },
+ updateElement: function(point, index, reset) {
+ var me = this;
+ var custom = point.custom || {};
+ var dataset = me.getDataset();
+ var scale = me.chart.scale;
+ var pointElementOptions = me.chart.options.elements.point;
+ var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
+
+ // Compatibility: If the properties are defined with only the old name, use those values
+ if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
+ dataset.pointRadius = dataset.radius;
+ }
+ if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) {
+ dataset.pointHitRadius = dataset.hitRadius;
+ }
+
+ helpers.extend(point, {
+ // Utility
+ _datasetIndex: me.index,
+ _index: index,
+ _scale: scale,
+
+ // Desired view properties
+ _model: {
+ x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales
+ y: reset ? scale.yCenter : pointPosition.y,
+
+ // Appearance
+ tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension),
+ radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius),
+ backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor),
+ borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor),
+ borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth),
+ pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle),
+
+ // Tooltip
+ hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius)
+ }
+ });
+
+ point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y));
+ },
+ updateBezierControlPoints: function() {
+ var chartArea = this.chart.chartArea;
+ var meta = this.getMeta();
+
+ helpers.each(meta.data, function(point, index) {
+ var model = point._model;
+ var controlPoints = helpers.splineCurve(
+ helpers.previousItem(meta.data, index, true)._model,
+ model,
+ helpers.nextItem(meta.data, index, true)._model,
+ model.tension
+ );
+
+ // Prevent the bezier going outside of the bounds of the graph
+ model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left);
+ model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top);
+
+ model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left);
+ model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top);
+
+ // Now pivot the point for animation
+ point.pivot();
+ });
+ },
+
+ setHoverStyle: function(point) {
+ // Point
+ var dataset = this.chart.data.datasets[point._datasetIndex];
+ var custom = point.custom || {};
+ var index = point._index;
+ var model = point._model;
+
+ model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius);
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor));
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor));
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth);
+ },
+
+ removeHoverStyle: function(point) {
+ var dataset = this.chart.data.datasets[point._datasetIndex];
+ var custom = point.custom || {};
+ var index = point._index;
+ var model = point._model;
+ var pointElementOptions = this.chart.options.elements.point;
+
+ model.radius = custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius);
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor);
+ model.borderColor = custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor);
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth);
+ }
+ });
+};
+
+},{"25":25,"40":40,"45":45}],21:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+
+defaults._set('scatter', {
+ hover: {
+ mode: 'single'
+ },
+
+ scales: {
+ xAxes: [{
+ id: 'x-axis-1', // need an ID so datasets can reference the scale
+ type: 'linear', // scatter should not use a category axis
+ position: 'bottom'
+ }],
+ yAxes: [{
+ id: 'y-axis-1',
+ type: 'linear',
+ position: 'left'
+ }]
+ },
+
+ showLines: false,
+
+ tooltips: {
+ callbacks: {
+ title: function() {
+ return ''; // doesn't make sense for scatter since data are formatted as a point
+ },
+ label: function(item) {
+ return '(' + item.xLabel + ', ' + item.yLabel + ')';
+ }
+ }
+ }
+});
+
+module.exports = function(Chart) {
+
+ // Scatter charts use line controllers
+ Chart.controllers.scatter = Chart.controllers.line;
+
+};
+
+},{"25":25}],22:[function(require,module,exports){
+/* global window: false */
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+
+defaults._set('global', {
+ animation: {
+ duration: 1000,
+ easing: 'easeOutQuart',
+ onProgress: helpers.noop,
+ onComplete: helpers.noop
+ }
+});
+
+module.exports = function(Chart) {
+
+ Chart.Animation = Element.extend({
+ chart: null, // the animation associated chart instance
+ currentStep: 0, // the current animation step
+ numSteps: 60, // default number of steps
+ easing: '', // the easing to use for this animation
+ render: null, // render function used by the animation service
+
+ onAnimationProgress: null, // user specified callback to fire on each step of the animation
+ onAnimationComplete: null, // user specified callback to fire when the animation finishes
+ });
+
+ Chart.animationService = {
+ frameDuration: 17,
+ animations: [],
+ dropFrames: 0,
+ request: null,
+
+ /**
+ * @param {Chart} chart - The chart to animate.
+ * @param {Chart.Animation} animation - The animation that we will animate.
+ * @param {Number} duration - The animation duration in ms.
+ * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
+ */
+ addAnimation: function(chart, animation, duration, lazy) {
+ var animations = this.animations;
+ var i, ilen;
+
+ animation.chart = chart;
+
+ if (!lazy) {
+ chart.animating = true;
+ }
+
+ for (i = 0, ilen = animations.length; i < ilen; ++i) {
+ if (animations[i].chart === chart) {
+ animations[i] = animation;
+ return;
+ }
+ }
+
+ animations.push(animation);
+
+ // If there are no animations queued, manually kickstart a digest, for lack of a better word
+ if (animations.length === 1) {
+ this.requestAnimationFrame();
+ }
+ },
+
+ cancelAnimation: function(chart) {
+ var index = helpers.findIndex(this.animations, function(animation) {
+ return animation.chart === chart;
+ });
+
+ if (index !== -1) {
+ this.animations.splice(index, 1);
+ chart.animating = false;
+ }
+ },
+
+ requestAnimationFrame: function() {
+ var me = this;
+ if (me.request === null) {
+ // Skip animation frame requests until the active one is executed.
+ // This can happen when processing mouse events, e.g. 'mousemove'
+ // and 'mouseout' events will trigger multiple renders.
+ me.request = helpers.requestAnimFrame.call(window, function() {
+ me.request = null;
+ me.startDigest();
+ });
+ }
+ },
+
+ /**
+ * @private
+ */
+ startDigest: function() {
+ var me = this;
+ var startTime = Date.now();
+ var framesToDrop = 0;
+
+ if (me.dropFrames > 1) {
+ framesToDrop = Math.floor(me.dropFrames);
+ me.dropFrames = me.dropFrames % 1;
+ }
+
+ me.advance(1 + framesToDrop);
+
+ var endTime = Date.now();
+
+ me.dropFrames += (endTime - startTime) / me.frameDuration;
+
+ // Do we have more stuff to animate?
+ if (me.animations.length > 0) {
+ me.requestAnimationFrame();
+ }
+ },
+
+ /**
+ * @private
+ */
+ advance: function(count) {
+ var animations = this.animations;
+ var animation, chart;
+ var i = 0;
+
+ while (i < animations.length) {
+ animation = animations[i];
+ chart = animation.chart;
+
+ animation.currentStep = (animation.currentStep || 0) + count;
+ animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
+
+ helpers.callback(animation.render, [chart, animation], chart);
+ helpers.callback(animation.onAnimationProgress, [animation], chart);
+
+ if (animation.currentStep >= animation.numSteps) {
+ helpers.callback(animation.onAnimationComplete, [animation], chart);
+ chart.animating = false;
+ animations.splice(i, 1);
+ } else {
+ ++i;
+ }
+ }
+ }
+ };
+
+ /**
+ * Provided for backward compatibility, use Chart.Animation instead
+ * @prop Chart.Animation#animationObject
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ */
+ Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
+ get: function() {
+ return this;
+ }
+ });
+
+ /**
+ * Provided for backward compatibility, use Chart.Animation#chart instead
+ * @prop Chart.Animation#chartInstance
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ */
+ Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
+ get: function() {
+ return this.chart;
+ },
+ set: function(value) {
+ this.chart = value;
+ }
+ });
+
+};
+
+},{"25":25,"26":26,"45":45}],23:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var helpers = require(45);
+var Interaction = require(28);
+var platform = require(48);
+
+module.exports = function(Chart) {
+ var plugins = Chart.plugins;
+
+ // Create a dictionary of chart types, to allow for extension of existing types
+ Chart.types = {};
+
+ // Store a reference to each instance - allowing us to globally resize chart instances on window resize.
+ // Destroy method on the chart will remove the instance of the chart from this reference.
+ Chart.instances = {};
+
+ // Controllers available for dataset visualization eg. bar, line, slice, etc.
+ Chart.controllers = {};
+
+ /**
+ * Initializes the given config with global and chart default values.
+ */
+ function initConfig(config) {
+ config = config || {};
+
+ // Do NOT use configMerge() for the data object because this method merges arrays
+ // and so would change references to labels and datasets, preventing data updates.
+ var data = config.data = config.data || {};
+ data.datasets = data.datasets || [];
+ data.labels = data.labels || [];
+
+ config.options = helpers.configMerge(
+ defaults.global,
+ defaults[config.type],
+ config.options || {});
+
+ return config;
+ }
+
+ /**
+ * Updates the config of the chart
+ * @param chart {Chart} chart to update the options for
+ */
+ function updateConfig(chart) {
+ var newOptions = chart.options;
+
+ // Update Scale(s) with options
+ if (newOptions.scale) {
+ chart.scale.options = newOptions.scale;
+ } else if (newOptions.scales) {
+ newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {
+ chart.scales[scaleOptions.id].options = scaleOptions;
+ });
+ }
+
+ // Tooltip
+ chart.tooltip._options = newOptions.tooltips;
+ }
+
+ function positionIsHorizontal(position) {
+ return position === 'top' || position === 'bottom';
+ }
+
+ helpers.extend(Chart.prototype, /** @lends Chart */ {
+ /**
+ * @private
+ */
+ construct: function(item, config) {
+ var me = this;
+
+ config = initConfig(config);
+
+ var context = platform.acquireContext(item, config);
+ var canvas = context && context.canvas;
+ var height = canvas && canvas.height;
+ var width = canvas && canvas.width;
+
+ me.id = helpers.uid();
+ me.ctx = context;
+ me.canvas = canvas;
+ me.config = config;
+ me.width = width;
+ me.height = height;
+ me.aspectRatio = height ? width / height : null;
+ me.options = config.options;
+ me._bufferedRender = false;
+
+ /**
+ * Provided for backward compatibility, Chart and Chart.Controller have been merged,
+ * the "instance" still need to be defined since it might be called from plugins.
+ * @prop Chart#chart
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ * @private
+ */
+ me.chart = me;
+ me.controller = me; // chart.chart.controller #inception
+
+ // Add the chart instance to the global namespace
+ Chart.instances[me.id] = me;
+
+ // Define alias to the config data: `chart.data === chart.config.data`
+ Object.defineProperty(me, 'data', {
+ get: function() {
+ return me.config.data;
+ },
+ set: function(value) {
+ me.config.data = value;
+ }
+ });
+
+ if (!context || !canvas) {
+ // The given item is not a compatible context2d element, let's return before finalizing
+ // the chart initialization but after setting basic chart / controller properties that
+ // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
+ // https://github.com/chartjs/Chart.js/issues/2807
+ console.error("Failed to create chart: can't acquire context from the given item");
+ return;
+ }
+
+ me.initialize();
+ me.update();
+ },
+
+ /**
+ * @private
+ */
+ initialize: function() {
+ var me = this;
+
+ // Before init plugin notification
+ plugins.notify(me, 'beforeInit');
+
+ helpers.retinaScale(me, me.options.devicePixelRatio);
+
+ me.bindEvents();
+
+ if (me.options.responsive) {
+ // Initial resize before chart draws (must be silent to preserve initial animations).
+ me.resize(true);
+ }
+
+ // Make sure scales have IDs and are built before we build any controllers.
+ me.ensureScalesHaveIDs();
+ me.buildScales();
+ me.initToolTip();
+
+ // After init plugin notification
+ plugins.notify(me, 'afterInit');
+
+ return me;
+ },
+
+ clear: function() {
+ helpers.canvas.clear(this);
+ return this;
+ },
+
+ stop: function() {
+ // Stops any current animation loop occurring
+ Chart.animationService.cancelAnimation(this);
+ return this;
+ },
+
+ resize: function(silent) {
+ var me = this;
+ var options = me.options;
+ var canvas = me.canvas;
+ var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
+
+ // the canvas render width and height will be casted to integers so make sure that
+ // the canvas display style uses the same integer values to avoid blurring effect.
+
+ // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased
+ var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
+ var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
+
+ if (me.width === newWidth && me.height === newHeight) {
+ return;
+ }
+
+ canvas.width = me.width = newWidth;
+ canvas.height = me.height = newHeight;
+ canvas.style.width = newWidth + 'px';
+ canvas.style.height = newHeight + 'px';
+
+ helpers.retinaScale(me, options.devicePixelRatio);
+
+ if (!silent) {
+ // Notify any plugins about the resize
+ var newSize = {width: newWidth, height: newHeight};
+ plugins.notify(me, 'resize', [newSize]);
+
+ // Notify of resize
+ if (me.options.onResize) {
+ me.options.onResize(me, newSize);
+ }
+
+ me.stop();
+ me.update(me.options.responsiveAnimationDuration);
+ }
+ },
+
+ ensureScalesHaveIDs: function() {
+ var options = this.options;
+ var scalesOptions = options.scales || {};
+ var scaleOptions = options.scale;
+
+ helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
+ xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
+ });
+
+ helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
+ yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
+ });
+
+ if (scaleOptions) {
+ scaleOptions.id = scaleOptions.id || 'scale';
+ }
+ },
+
+ /**
+ * Builds a map of scale ID to scale object for future lookup.
+ */
+ buildScales: function() {
+ var me = this;
+ var options = me.options;
+ var scales = me.scales = {};
+ var items = [];
+
+ if (options.scales) {
+ items = items.concat(
+ (options.scales.xAxes || []).map(function(xAxisOptions) {
+ return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
+ }),
+ (options.scales.yAxes || []).map(function(yAxisOptions) {
+ return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
+ })
+ );
+ }
+
+ if (options.scale) {
+ items.push({
+ options: options.scale,
+ dtype: 'radialLinear',
+ isDefault: true,
+ dposition: 'chartArea'
+ });
+ }
+
+ helpers.each(items, function(item) {
+ var scaleOptions = item.options;
+ var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
+ var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
+ if (!scaleClass) {
+ return;
+ }
+
+ if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
+ scaleOptions.position = item.dposition;
+ }
+
+ var scale = new scaleClass({
+ id: scaleOptions.id,
+ options: scaleOptions,
+ ctx: me.ctx,
+ chart: me
+ });
+
+ scales[scale.id] = scale;
+ scale.mergeTicksOptions();
+
+ // TODO(SB): I think we should be able to remove this custom case (options.scale)
+ // and consider it as a regular scale part of the "scales"" map only! This would
+ // make the logic easier and remove some useless? custom code.
+ if (item.isDefault) {
+ me.scale = scale;
+ }
+ });
+
+ Chart.scaleService.addScalesToLayout(this);
+ },
+
+ buildOrUpdateControllers: function() {
+ var me = this;
+ var types = [];
+ var newControllers = [];
+
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ var meta = me.getDatasetMeta(datasetIndex);
+ var type = dataset.type || me.config.type;
+
+ if (meta.type && meta.type !== type) {
+ me.destroyDatasetMeta(datasetIndex);
+ meta = me.getDatasetMeta(datasetIndex);
+ }
+ meta.type = type;
+
+ types.push(meta.type);
+
+ if (meta.controller) {
+ meta.controller.updateIndex(datasetIndex);
+ } else {
+ var ControllerClass = Chart.controllers[meta.type];
+ if (ControllerClass === undefined) {
+ throw new Error('"' + meta.type + '" is not a chart type.');
+ }
+
+ meta.controller = new ControllerClass(me, datasetIndex);
+ newControllers.push(meta.controller);
+ }
+ }, me);
+
+ return newControllers;
+ },
+
+ /**
+ * Reset the elements of all datasets
+ * @private
+ */
+ resetElements: function() {
+ var me = this;
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ me.getDatasetMeta(datasetIndex).controller.reset();
+ }, me);
+ },
+
+ /**
+ * Resets the chart back to it's state before the initial animation
+ */
+ reset: function() {
+ this.resetElements();
+ this.tooltip.initialize();
+ },
+
+ update: function(config) {
+ var me = this;
+
+ if (!config || typeof config !== 'object') {
+ // backwards compatibility
+ config = {
+ duration: config,
+ lazy: arguments[1]
+ };
+ }
+
+ updateConfig(me);
+
+ if (plugins.notify(me, 'beforeUpdate') === false) {
+ return;
+ }
+
+ // In case the entire data object changed
+ me.tooltip._data = me.data;
+
+ // Make sure dataset controllers are updated and new controllers are reset
+ var newControllers = me.buildOrUpdateControllers();
+
+ // Make sure all dataset controllers have correct meta data counts
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
+ }, me);
+
+ me.updateLayout();
+
+ // Can only reset the new controllers after the scales have been updated
+ helpers.each(newControllers, function(controller) {
+ controller.reset();
+ });
+
+ me.updateDatasets();
+
+ // Need to reset tooltip in case it is displayed with elements that are removed
+ // after update.
+ me.tooltip.initialize();
+
+ // Last active contains items that were previously in the tooltip.
+ // When we reset the tooltip, we need to clear it
+ me.lastActive = [];
+
+ // Do this before render so that any plugins that need final scale updates can use it
+ plugins.notify(me, 'afterUpdate');
+
+ if (me._bufferedRender) {
+ me._bufferedRequest = {
+ duration: config.duration,
+ easing: config.easing,
+ lazy: config.lazy
+ };
+ } else {
+ me.render(config);
+ }
+ },
+
+ /**
+ * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
+ * hook, in which case, plugins will not be called on `afterLayout`.
+ * @private
+ */
+ updateLayout: function() {
+ var me = this;
+
+ if (plugins.notify(me, 'beforeLayout') === false) {
+ return;
+ }
+
+ Chart.layoutService.update(this, this.width, this.height);
+
+ /**
+ * Provided for backward compatibility, use `afterLayout` instead.
+ * @method IPlugin#afterScaleUpdate
+ * @deprecated since version 2.5.0
+ * @todo remove at version 3
+ * @private
+ */
+ plugins.notify(me, 'afterScaleUpdate');
+ plugins.notify(me, 'afterLayout');
+ },
+
+ /**
+ * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
+ * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
+ * @private
+ */
+ updateDatasets: function() {
+ var me = this;
+
+ if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
+ return;
+ }
+
+ for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
+ me.updateDataset(i);
+ }
+
+ plugins.notify(me, 'afterDatasetsUpdate');
+ },
+
+ /**
+ * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
+ * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
+ * @private
+ */
+ updateDataset: function(index) {
+ var me = this;
+ var meta = me.getDatasetMeta(index);
+ var args = {
+ meta: meta,
+ index: index
+ };
+
+ if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
+ return;
+ }
+
+ meta.controller.update();
+
+ plugins.notify(me, 'afterDatasetUpdate', [args]);
+ },
+
+ render: function(config) {
+ var me = this;
+
+ if (!config || typeof config !== 'object') {
+ // backwards compatibility
+ config = {
+ duration: config,
+ lazy: arguments[1]
+ };
+ }
+
+ var duration = config.duration;
+ var lazy = config.lazy;
+
+ if (plugins.notify(me, 'beforeRender') === false) {
+ return;
+ }
+
+ var animationOptions = me.options.animation;
+ var onComplete = function(animation) {
+ plugins.notify(me, 'afterRender');
+ helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
+ };
+
+ if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
+ var animation = new Chart.Animation({
+ numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
+ easing: config.easing || animationOptions.easing,
+
+ render: function(chart, animationObject) {
+ var easingFunction = helpers.easing.effects[animationObject.easing];
+ var currentStep = animationObject.currentStep;
+ var stepDecimal = currentStep / animationObject.numSteps;
+
+ chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
+ },
+
+ onAnimationProgress: animationOptions.onProgress,
+ onAnimationComplete: onComplete
+ });
+
+ Chart.animationService.addAnimation(me, animation, duration, lazy);
+ } else {
+ me.draw();
+
+ // See https://github.com/chartjs/Chart.js/issues/3781
+ onComplete(new Chart.Animation({numSteps: 0, chart: me}));
+ }
+
+ return me;
+ },
+
+ draw: function(easingValue) {
+ var me = this;
+
+ me.clear();
+
+ if (helpers.isNullOrUndef(easingValue)) {
+ easingValue = 1;
+ }
+
+ me.transition(easingValue);
+
+ if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
+ return;
+ }
+
+ // Draw all the scales
+ helpers.each(me.boxes, function(box) {
+ box.draw(me.chartArea);
+ }, me);
+
+ if (me.scale) {
+ me.scale.draw();
+ }
+
+ me.drawDatasets(easingValue);
+ me._drawTooltip(easingValue);
+
+ plugins.notify(me, 'afterDraw', [easingValue]);
+ },
+
+ /**
+ * @private
+ */
+ transition: function(easingValue) {
+ var me = this;
+
+ for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
+ if (me.isDatasetVisible(i)) {
+ me.getDatasetMeta(i).controller.transition(easingValue);
+ }
+ }
+
+ me.tooltip.transition(easingValue);
+ },
+
+ /**
+ * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
+ * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
+ * @private
+ */
+ drawDatasets: function(easingValue) {
+ var me = this;
+
+ if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
+ return;
+ }
+
+ // Draw datasets reversed to support proper line stacking
+ for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
+ if (me.isDatasetVisible(i)) {
+ me.drawDataset(i, easingValue);
+ }
+ }
+
+ plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
+ },
+
+ /**
+ * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
+ * hook, in which case, plugins will not be called on `afterDatasetDraw`.
+ * @private
+ */
+ drawDataset: function(index, easingValue) {
+ var me = this;
+ var meta = me.getDatasetMeta(index);
+ var args = {
+ meta: meta,
+ index: index,
+ easingValue: easingValue
+ };
+
+ if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
+ return;
+ }
+
+ meta.controller.draw(easingValue);
+
+ plugins.notify(me, 'afterDatasetDraw', [args]);
+ },
+
+ /**
+ * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
+ * hook, in which case, plugins will not be called on `afterTooltipDraw`.
+ * @private
+ */
+ _drawTooltip: function(easingValue) {
+ var me = this;
+ var tooltip = me.tooltip;
+ var args = {
+ tooltip: tooltip,
+ easingValue: easingValue
+ };
+
+ if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
+ return;
+ }
+
+ tooltip.draw();
+
+ plugins.notify(me, 'afterTooltipDraw', [args]);
+ },
+
+ // Get the single element that was clicked on
+ // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
+ getElementAtEvent: function(e) {
+ return Interaction.modes.single(this, e);
+ },
+
+ getElementsAtEvent: function(e) {
+ return Interaction.modes.label(this, e, {intersect: true});
+ },
+
+ getElementsAtXAxis: function(e) {
+ return Interaction.modes['x-axis'](this, e, {intersect: true});
+ },
+
+ getElementsAtEventForMode: function(e, mode, options) {
+ var method = Interaction.modes[mode];
+ if (typeof method === 'function') {
+ return method(this, e, options);
+ }
+
+ return [];
+ },
+
+ getDatasetAtEvent: function(e) {
+ return Interaction.modes.dataset(this, e, {intersect: true});
+ },
+
+ getDatasetMeta: function(datasetIndex) {
+ var me = this;
+ var dataset = me.data.datasets[datasetIndex];
+ if (!dataset._meta) {
+ dataset._meta = {};
+ }
+
+ var meta = dataset._meta[me.id];
+ if (!meta) {
+ meta = dataset._meta[me.id] = {
+ type: null,
+ data: [],
+ dataset: null,
+ controller: null,
+ hidden: null, // See isDatasetVisible() comment
+ xAxisID: null,
+ yAxisID: null
+ };
+ }
+
+ return meta;
+ },
+
+ getVisibleDatasetCount: function() {
+ var count = 0;
+ for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
+ if (this.isDatasetVisible(i)) {
+ count++;
+ }
+ }
+ return count;
+ },
+
+ isDatasetVisible: function(datasetIndex) {
+ var meta = this.getDatasetMeta(datasetIndex);
+
+ // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
+ // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
+ return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
+ },
+
+ generateLegend: function() {
+ return this.options.legendCallback(this);
+ },
+
+ /**
+ * @private
+ */
+ destroyDatasetMeta: function(datasetIndex) {
+ var id = this.id;
+ var dataset = this.data.datasets[datasetIndex];
+ var meta = dataset._meta && dataset._meta[id];
+
+ if (meta) {
+ meta.controller.destroy();
+ delete dataset._meta[id];
+ }
+ },
+
+ destroy: function() {
+ var me = this;
+ var canvas = me.canvas;
+ var i, ilen;
+
+ me.stop();
+
+ // dataset controllers need to cleanup associated data
+ for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
+ me.destroyDatasetMeta(i);
+ }
+
+ if (canvas) {
+ me.unbindEvents();
+ helpers.canvas.clear(me);
+ platform.releaseContext(me.ctx);
+ me.canvas = null;
+ me.ctx = null;
+ }
+
+ plugins.notify(me, 'destroy');
+
+ delete Chart.instances[me.id];
+ },
+
+ toBase64Image: function() {
+ return this.canvas.toDataURL.apply(this.canvas, arguments);
+ },
+
+ initToolTip: function() {
+ var me = this;
+ me.tooltip = new Chart.Tooltip({
+ _chart: me,
+ _chartInstance: me, // deprecated, backward compatibility
+ _data: me.data,
+ _options: me.options.tooltips
+ }, me);
+ },
+
+ /**
+ * @private
+ */
+ bindEvents: function() {
+ var me = this;
+ var listeners = me._listeners = {};
+ var listener = function() {
+ me.eventHandler.apply(me, arguments);
+ };
+
+ helpers.each(me.options.events, function(type) {
+ platform.addEventListener(me, type, listener);
+ listeners[type] = listener;
+ });
+
+ // Elements used to detect size change should not be injected for non responsive charts.
+ // See https://github.com/chartjs/Chart.js/issues/2210
+ if (me.options.responsive) {
+ listener = function() {
+ me.resize();
+ };
+
+ platform.addEventListener(me, 'resize', listener);
+ listeners.resize = listener;
+ }
+ },
+
+ /**
+ * @private
+ */
+ unbindEvents: function() {
+ var me = this;
+ var listeners = me._listeners;
+ if (!listeners) {
+ return;
+ }
+
+ delete me._listeners;
+ helpers.each(listeners, function(listener, type) {
+ platform.removeEventListener(me, type, listener);
+ });
+ },
+
+ updateHoverStyle: function(elements, mode, enabled) {
+ var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
+ var element, i, ilen;
+
+ for (i = 0, ilen = elements.length; i < ilen; ++i) {
+ element = elements[i];
+ if (element) {
+ this.getDatasetMeta(element._datasetIndex).controller[method](element);
+ }
+ }
+ },
+
+ /**
+ * @private
+ */
+ eventHandler: function(e) {
+ var me = this;
+ var tooltip = me.tooltip;
+
+ if (plugins.notify(me, 'beforeEvent', [e]) === false) {
+ return;
+ }
+
+ // Buffer any update calls so that renders do not occur
+ me._bufferedRender = true;
+ me._bufferedRequest = null;
+
+ var changed = me.handleEvent(e);
+ changed |= tooltip && tooltip.handleEvent(e);
+
+ plugins.notify(me, 'afterEvent', [e]);
+
+ var bufferedRequest = me._bufferedRequest;
+ if (bufferedRequest) {
+ // If we have an update that was triggered, we need to do a normal render
+ me.render(bufferedRequest);
+ } else if (changed && !me.animating) {
+ // If entering, leaving, or changing elements, animate the change via pivot
+ me.stop();
+
+ // We only need to render at this point. Updating will cause scales to be
+ // recomputed generating flicker & using more memory than necessary.
+ me.render(me.options.hover.animationDuration, true);
+ }
+
+ me._bufferedRender = false;
+ me._bufferedRequest = null;
+
+ return me;
+ },
+
+ /**
+ * Handle an event
+ * @private
+ * @param {IEvent} event the event to handle
+ * @return {Boolean} true if the chart needs to re-render
+ */
+ handleEvent: function(e) {
+ var me = this;
+ var options = me.options || {};
+ var hoverOptions = options.hover;
+ var changed = false;
+
+ me.lastActive = me.lastActive || [];
+
+ // Find Active Elements for hover and tooltips
+ if (e.type === 'mouseout') {
+ me.active = [];
+ } else {
+ me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
+ }
+
+ // Invoke onHover hook
+ // Need to call with native event here to not break backwards compatibility
+ helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
+
+ if (e.type === 'mouseup' || e.type === 'click') {
+ if (options.onClick) {
+ // Use e.native here for backwards compatibility
+ options.onClick.call(me, e.native, me.active);
+ }
+ }
+
+ // Remove styling for last active (even if it may still be active)
+ if (me.lastActive.length) {
+ me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
+ }
+
+ // Built in hover styling
+ if (me.active.length && hoverOptions.mode) {
+ me.updateHoverStyle(me.active, hoverOptions.mode, true);
+ }
+
+ changed = !helpers.arrayEquals(me.active, me.lastActive);
+
+ // Remember Last Actives
+ me.lastActive = me.active;
+
+ return changed;
+ }
+ });
+
+ /**
+ * Provided for backward compatibility, use Chart instead.
+ * @class Chart.Controller
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ * @private
+ */
+ Chart.Controller = Chart;
+};
+
+},{"25":25,"28":28,"45":45,"48":48}],24:[function(require,module,exports){
+'use strict';
+
+var helpers = require(45);
+
+module.exports = function(Chart) {
+
+ var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
+
+ /**
+ * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
+ * 'unshift') and notify the listener AFTER the array has been altered. Listeners are
+ * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
+ */
+ function listenArrayEvents(array, listener) {
+ if (array._chartjs) {
+ array._chartjs.listeners.push(listener);
+ return;
+ }
+
+ Object.defineProperty(array, '_chartjs', {
+ configurable: true,
+ enumerable: false,
+ value: {
+ listeners: [listener]
+ }
+ });
+
+ arrayEvents.forEach(function(key) {
+ var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
+ var base = array[key];
+
+ Object.defineProperty(array, key, {
+ configurable: true,
+ enumerable: false,
+ value: function() {
+ var args = Array.prototype.slice.call(arguments);
+ var res = base.apply(this, args);
+
+ helpers.each(array._chartjs.listeners, function(object) {
+ if (typeof object[method] === 'function') {
+ object[method].apply(object, args);
+ }
+ });
+
+ return res;
+ }
+ });
+ });
+ }
+
+ /**
+ * Removes the given array event listener and cleanup extra attached properties (such as
+ * the _chartjs stub and overridden methods) if array doesn't have any more listeners.
+ */
+ function unlistenArrayEvents(array, listener) {
+ var stub = array._chartjs;
+ if (!stub) {
+ return;
+ }
+
+ var listeners = stub.listeners;
+ var index = listeners.indexOf(listener);
+ if (index !== -1) {
+ listeners.splice(index, 1);
+ }
+
+ if (listeners.length > 0) {
+ return;
+ }
+
+ arrayEvents.forEach(function(key) {
+ delete array[key];
+ });
+
+ delete array._chartjs;
+ }
+
+ // Base class for all dataset controllers (line, bar, etc)
+ Chart.DatasetController = function(chart, datasetIndex) {
+ this.initialize(chart, datasetIndex);
+ };
+
+ helpers.extend(Chart.DatasetController.prototype, {
+
+ /**
+ * Element type used to generate a meta dataset (e.g. Chart.element.Line).
+ * @type {Chart.core.element}
+ */
+ datasetElementType: null,
+
+ /**
+ * Element type used to generate a meta data (e.g. Chart.element.Point).
+ * @type {Chart.core.element}
+ */
+ dataElementType: null,
+
+ initialize: function(chart, datasetIndex) {
+ var me = this;
+ me.chart = chart;
+ me.index = datasetIndex;
+ me.linkScales();
+ me.addElements();
+ },
+
+ updateIndex: function(datasetIndex) {
+ this.index = datasetIndex;
+ },
+
+ linkScales: function() {
+ var me = this;
+ var meta = me.getMeta();
+ var dataset = me.getDataset();
+
+ if (meta.xAxisID === null) {
+ meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
+ }
+ if (meta.yAxisID === null) {
+ meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;
+ }
+ },
+
+ getDataset: function() {
+ return this.chart.data.datasets[this.index];
+ },
+
+ getMeta: function() {
+ return this.chart.getDatasetMeta(this.index);
+ },
+
+ getScaleForId: function(scaleID) {
+ return this.chart.scales[scaleID];
+ },
+
+ reset: function() {
+ this.update(true);
+ },
+
+ /**
+ * @private
+ */
+ destroy: function() {
+ if (this._data) {
+ unlistenArrayEvents(this._data, this);
+ }
+ },
+
+ createMetaDataset: function() {
+ var me = this;
+ var type = me.datasetElementType;
+ return type && new type({
+ _chart: me.chart,
+ _datasetIndex: me.index
+ });
+ },
+
+ createMetaData: function(index) {
+ var me = this;
+ var type = me.dataElementType;
+ return type && new type({
+ _chart: me.chart,
+ _datasetIndex: me.index,
+ _index: index
+ });
+ },
+
+ addElements: function() {
+ var me = this;
+ var meta = me.getMeta();
+ var data = me.getDataset().data || [];
+ var metaData = meta.data;
+ var i, ilen;
+
+ for (i = 0, ilen = data.length; i < ilen; ++i) {
+ metaData[i] = metaData[i] || me.createMetaData(i);
+ }
+
+ meta.dataset = meta.dataset || me.createMetaDataset();
+ },
+
+ addElementAndReset: function(index) {
+ var element = this.createMetaData(index);
+ this.getMeta().data.splice(index, 0, element);
+ this.updateElement(element, index, true);
+ },
+
+ buildOrUpdateElements: function() {
+ var me = this;
+ var dataset = me.getDataset();
+ var data = dataset.data || (dataset.data = []);
+
+ // In order to correctly handle data addition/deletion animation (an thus simulate
+ // real-time charts), we need to monitor these data modifications and synchronize
+ // the internal meta data accordingly.
+ if (me._data !== data) {
+ if (me._data) {
+ // This case happens when the user replaced the data array instance.
+ unlistenArrayEvents(me._data, me);
+ }
+
+ listenArrayEvents(data, me);
+ me._data = data;
+ }
+
+ // Re-sync meta data in case the user replaced the data array or if we missed
+ // any updates and so make sure that we handle number of datapoints changing.
+ me.resyncElements();
+ },
+
+ update: helpers.noop,
+
+ transition: function(easingValue) {
+ var meta = this.getMeta();
+ var elements = meta.data || [];
+ var ilen = elements.length;
+ var i = 0;
+
+ for (; i < ilen; ++i) {
+ elements[i].transition(easingValue);
+ }
+
+ if (meta.dataset) {
+ meta.dataset.transition(easingValue);
+ }
+ },
+
+ draw: function() {
+ var meta = this.getMeta();
+ var elements = meta.data || [];
+ var ilen = elements.length;
+ var i = 0;
+
+ if (meta.dataset) {
+ meta.dataset.draw();
+ }
+
+ for (; i < ilen; ++i) {
+ elements[i].draw();
+ }
+ },
+
+ removeHoverStyle: function(element, elementOpts) {
+ var dataset = this.chart.data.datasets[element._datasetIndex];
+ var index = element._index;
+ var custom = element.custom || {};
+ var valueOrDefault = helpers.valueAtIndexOrDefault;
+ var model = element._model;
+
+ model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor);
+ model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor);
+ model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth);
+ },
+
+ setHoverStyle: function(element) {
+ var dataset = this.chart.data.datasets[element._datasetIndex];
+ var index = element._index;
+ var custom = element.custom || {};
+ var valueOrDefault = helpers.valueAtIndexOrDefault;
+ var getHoverColor = helpers.getHoverColor;
+ var model = element._model;
+
+ model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor));
+ model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor));
+ model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth);
+ },
+
+ /**
+ * @private
+ */
+ resyncElements: function() {
+ var me = this;
+ var meta = me.getMeta();
+ var data = me.getDataset().data;
+ var numMeta = meta.data.length;
+ var numData = data.length;
+
+ if (numData < numMeta) {
+ meta.data.splice(numData, numMeta - numData);
+ } else if (numData > numMeta) {
+ me.insertElements(numMeta, numData - numMeta);
+ }
+ },
+
+ /**
+ * @private
+ */
+ insertElements: function(start, count) {
+ for (var i = 0; i < count; ++i) {
+ this.addElementAndReset(start + i);
+ }
+ },
+
+ /**
+ * @private
+ */
+ onDataPush: function() {
+ this.insertElements(this.getDataset().data.length - 1, arguments.length);
+ },
+
+ /**
+ * @private
+ */
+ onDataPop: function() {
+ this.getMeta().data.pop();
+ },
+
+ /**
+ * @private
+ */
+ onDataShift: function() {
+ this.getMeta().data.shift();
+ },
+
+ /**
+ * @private
+ */
+ onDataSplice: function(start, count) {
+ this.getMeta().data.splice(start, count);
+ this.insertElements(start, arguments.length - 2);
+ },
+
+ /**
+ * @private
+ */
+ onDataUnshift: function() {
+ this.insertElements(0, arguments.length);
+ }
+ });
+
+ Chart.DatasetController.extend = helpers.inherits;
+};
+
+},{"45":45}],25:[function(require,module,exports){
+'use strict';
+
+var helpers = require(45);
+
+module.exports = {
+ /**
+ * @private
+ */
+ _set: function(scope, values) {
+ return helpers.merge(this[scope] || (this[scope] = {}), values);
+ }
+};
+
+},{"45":45}],26:[function(require,module,exports){
+'use strict';
+
+var color = require(3);
+var helpers = require(45);
+
+function interpolate(start, view, model, ease) {
+ var keys = Object.keys(model);
+ var i, ilen, key, actual, origin, target, type, c0, c1;
+
+ for (i = 0, ilen = keys.length; i < ilen; ++i) {
+ key = keys[i];
+
+ target = model[key];
+
+ // if a value is added to the model after pivot() has been called, the view
+ // doesn't contain it, so let's initialize the view to the target value.
+ if (!view.hasOwnProperty(key)) {
+ view[key] = target;
+ }
+
+ actual = view[key];
+
+ if (actual === target || key[0] === '_') {
+ continue;
+ }
+
+ if (!start.hasOwnProperty(key)) {
+ start[key] = actual;
+ }
+
+ origin = start[key];
+
+ type = typeof target;
+
+ if (type === typeof origin) {
+ if (type === 'string') {
+ c0 = color(origin);
+ if (c0.valid) {
+ c1 = color(target);
+ if (c1.valid) {
+ view[key] = c1.mix(c0, ease).rgbString();
+ continue;
+ }
+ }
+ } else if (type === 'number' && isFinite(origin) && isFinite(target)) {
+ view[key] = origin + (target - origin) * ease;
+ continue;
+ }
+ }
+
+ view[key] = target;
+ }
+}
+
+var Element = function(configuration) {
+ helpers.extend(this, configuration);
+ this.initialize.apply(this, arguments);
+};
+
+helpers.extend(Element.prototype, {
+
+ initialize: function() {
+ this.hidden = false;
+ },
+
+ pivot: function() {
+ var me = this;
+ if (!me._view) {
+ me._view = helpers.clone(me._model);
+ }
+ me._start = {};
+ return me;
+ },
+
+ transition: function(ease) {
+ var me = this;
+ var model = me._model;
+ var start = me._start;
+ var view = me._view;
+
+ // No animation -> No Transition
+ if (!model || ease === 1) {
+ me._view = model;
+ me._start = null;
+ return me;
+ }
+
+ if (!view) {
+ view = me._view = {};
+ }
+
+ if (!start) {
+ start = me._start = {};
+ }
+
+ interpolate(start, view, model, ease);
+
+ return me;
+ },
+
+ tooltipPosition: function() {
+ return {
+ x: this._model.x,
+ y: this._model.y
+ };
+ },
+
+ hasValue: function() {
+ return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
+ }
+});
+
+Element.extend = helpers.inherits;
+
+module.exports = Element;
+
+},{"3":3,"45":45}],27:[function(require,module,exports){
+/* global window: false */
+/* global document: false */
+'use strict';
+
+var color = require(3);
+var defaults = require(25);
+var helpers = require(45);
+
+module.exports = function(Chart) {
+
+ // -- Basic js utility methods
+
+ helpers.configMerge = function(/* objects ... */) {
+ return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
+ merger: function(key, target, source, options) {
+ var tval = target[key] || {};
+ var sval = source[key];
+
+ if (key === 'scales') {
+ // scale config merging is complex. Add our own function here for that
+ target[key] = helpers.scaleMerge(tval, sval);
+ } else if (key === 'scale') {
+ // used in polar area & radar charts since there is only one scale
+ target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]);
+ } else {
+ helpers._merger(key, target, source, options);
+ }
+ }
+ });
+ };
+
+ helpers.scaleMerge = function(/* objects ... */) {
+ return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), {
+ merger: function(key, target, source, options) {
+ if (key === 'xAxes' || key === 'yAxes') {
+ var slen = source[key].length;
+ var i, type, scale;
+
+ if (!target[key]) {
+ target[key] = [];
+ }
+
+ for (i = 0; i < slen; ++i) {
+ scale = source[key][i];
+ type = helpers.valueOrDefault(scale.type, key === 'xAxes' ? 'category' : 'linear');
+
+ if (i >= target[key].length) {
+ target[key].push({});
+ }
+
+ if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
+ // new/untyped scale or type changed: let's apply the new defaults
+ // then merge source scale to correctly overwrite the defaults.
+ helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]);
+ } else {
+ // scales type are the same
+ helpers.merge(target[key][i], scale);
+ }
+ }
+ } else {
+ helpers._merger(key, target, source, options);
+ }
+ }
+ });
+ };
+
+ helpers.where = function(collection, filterCallback) {
+ if (helpers.isArray(collection) && Array.prototype.filter) {
+ return collection.filter(filterCallback);
+ }
+ var filtered = [];
+
+ helpers.each(collection, function(item) {
+ if (filterCallback(item)) {
+ filtered.push(item);
+ }
+ });
+
+ return filtered;
+ };
+ helpers.findIndex = Array.prototype.findIndex ?
+ function(array, callback, scope) {
+ return array.findIndex(callback, scope);
+ } :
+ function(array, callback, scope) {
+ scope = scope === undefined ? array : scope;
+ for (var i = 0, ilen = array.length; i < ilen; ++i) {
+ if (callback.call(scope, array[i], i, array)) {
+ return i;
+ }
+ }
+ return -1;
+ };
+ helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
+ // Default to start of the array
+ if (helpers.isNullOrUndef(startIndex)) {
+ startIndex = -1;
+ }
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)) {
+ return currentItem;
+ }
+ }
+ };
+ helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
+ // Default to end of the array
+ if (helpers.isNullOrUndef(startIndex)) {
+ startIndex = arrayToSearch.length;
+ }
+ for (var i = startIndex - 1; i >= 0; i--) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)) {
+ return currentItem;
+ }
+ }
+ };
+
+ // -- Math methods
+ helpers.isNumber = function(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+ };
+ helpers.almostEquals = function(x, y, epsilon) {
+ return Math.abs(x - y) < epsilon;
+ };
+ helpers.almostWhole = function(x, epsilon) {
+ var rounded = Math.round(x);
+ return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
+ };
+ helpers.max = function(array) {
+ return array.reduce(function(max, value) {
+ if (!isNaN(value)) {
+ return Math.max(max, value);
+ }
+ return max;
+ }, Number.NEGATIVE_INFINITY);
+ };
+ helpers.min = function(array) {
+ return array.reduce(function(min, value) {
+ if (!isNaN(value)) {
+ return Math.min(min, value);
+ }
+ return min;
+ }, Number.POSITIVE_INFINITY);
+ };
+ helpers.sign = Math.sign ?
+ function(x) {
+ return Math.sign(x);
+ } :
+ function(x) {
+ x = +x; // convert to a number
+ if (x === 0 || isNaN(x)) {
+ return x;
+ }
+ return x > 0 ? 1 : -1;
+ };
+ helpers.log10 = Math.log10 ?
+ function(x) {
+ return Math.log10(x);
+ } :
+ function(x) {
+ return Math.log(x) / Math.LN10;
+ };
+ helpers.toRadians = function(degrees) {
+ return degrees * (Math.PI / 180);
+ };
+ helpers.toDegrees = function(radians) {
+ return radians * (180 / Math.PI);
+ };
+ // Gets the angle from vertical upright to the point about a centre.
+ helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
+ var distanceFromXCenter = anglePoint.x - centrePoint.x;
+ var distanceFromYCenter = anglePoint.y - centrePoint.y;
+ var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+ var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+ if (angle < (-0.5 * Math.PI)) {
+ angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
+ }
+
+ return {
+ angle: angle,
+ distance: radialDistanceFromCenter
+ };
+ };
+ helpers.distanceBetweenPoints = function(pt1, pt2) {
+ return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
+ };
+ helpers.aliasPixel = function(pixelWidth) {
+ return (pixelWidth % 2 === 0) ? 0 : 0.5;
+ };
+ helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
+ // Props to Rob Spencer at scaled innovation for his post on splining between points
+ // http://scaledinnovation.com/analytics/splines/aboutSplines.html
+
+ // This function must also respect "skipped" points
+
+ var previous = firstPoint.skip ? middlePoint : firstPoint;
+ var current = middlePoint;
+ var next = afterPoint.skip ? middlePoint : afterPoint;
+
+ var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
+ var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
+
+ var s01 = d01 / (d01 + d12);
+ var s12 = d12 / (d01 + d12);
+
+ // If all points are the same, s01 & s02 will be inf
+ s01 = isNaN(s01) ? 0 : s01;
+ s12 = isNaN(s12) ? 0 : s12;
+
+ var fa = t * s01; // scaling factor for triangle Ta
+ var fb = t * s12;
+
+ return {
+ previous: {
+ x: current.x - fa * (next.x - previous.x),
+ y: current.y - fa * (next.y - previous.y)
+ },
+ next: {
+ x: current.x + fb * (next.x - previous.x),
+ y: current.y + fb * (next.y - previous.y)
+ }
+ };
+ };
+ helpers.EPSILON = Number.EPSILON || 1e-14;
+ helpers.splineCurveMonotone = function(points) {
+ // This function calculates Bézier control points in a similar way than |splineCurve|,
+ // but preserves monotonicity of the provided data and ensures no local extremums are added
+ // between the dataset discrete points due to the interpolation.
+ // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
+
+ var pointsWithTangents = (points || []).map(function(point) {
+ return {
+ model: point._model,
+ deltaK: 0,
+ mK: 0
+ };
+ });
+
+ // Calculate slopes (deltaK) and initialize tangents (mK)
+ var pointsLen = pointsWithTangents.length;
+ var i, pointBefore, pointCurrent, pointAfter;
+ for (i = 0; i < pointsLen; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ if (pointCurrent.model.skip) {
+ continue;
+ }
+
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
+ if (pointAfter && !pointAfter.model.skip) {
+ var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
+
+ // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
+ pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
+ }
+
+ if (!pointBefore || pointBefore.model.skip) {
+ pointCurrent.mK = pointCurrent.deltaK;
+ } else if (!pointAfter || pointAfter.model.skip) {
+ pointCurrent.mK = pointBefore.deltaK;
+ } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
+ pointCurrent.mK = 0;
+ } else {
+ pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
+ }
+ }
+
+ // Adjust tangents to ensure monotonic properties
+ var alphaK, betaK, tauK, squaredMagnitude;
+ for (i = 0; i < pointsLen - 1; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ pointAfter = pointsWithTangents[i + 1];
+ if (pointCurrent.model.skip || pointAfter.model.skip) {
+ continue;
+ }
+
+ if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
+ pointCurrent.mK = pointAfter.mK = 0;
+ continue;
+ }
+
+ alphaK = pointCurrent.mK / pointCurrent.deltaK;
+ betaK = pointAfter.mK / pointCurrent.deltaK;
+ squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
+ if (squaredMagnitude <= 9) {
+ continue;
+ }
+
+ tauK = 3 / Math.sqrt(squaredMagnitude);
+ pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
+ pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
+ }
+
+ // Compute control points
+ var deltaX;
+ for (i = 0; i < pointsLen; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ if (pointCurrent.model.skip) {
+ continue;
+ }
+
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
+ if (pointBefore && !pointBefore.model.skip) {
+ deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
+ pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
+ pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
+ }
+ if (pointAfter && !pointAfter.model.skip) {
+ deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
+ pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
+ pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
+ }
+ }
+ };
+ helpers.nextItem = function(collection, index, loop) {
+ if (loop) {
+ return index >= collection.length - 1 ? collection[0] : collection[index + 1];
+ }
+ return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
+ };
+ helpers.previousItem = function(collection, index, loop) {
+ if (loop) {
+ return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
+ }
+ return index <= 0 ? collection[0] : collection[index - 1];
+ };
+ // Implementation of the nice number algorithm used in determining where axis labels will go
+ helpers.niceNum = function(range, round) {
+ var exponent = Math.floor(helpers.log10(range));
+ var fraction = range / Math.pow(10, exponent);
+ var niceFraction;
+
+ if (round) {
+ if (fraction < 1.5) {
+ niceFraction = 1;
+ } else if (fraction < 3) {
+ niceFraction = 2;
+ } else if (fraction < 7) {
+ niceFraction = 5;
+ } else {
+ niceFraction = 10;
+ }
+ } else if (fraction <= 1.0) {
+ niceFraction = 1;
+ } else if (fraction <= 2) {
+ niceFraction = 2;
+ } else if (fraction <= 5) {
+ niceFraction = 5;
+ } else {
+ niceFraction = 10;
+ }
+
+ return niceFraction * Math.pow(10, exponent);
+ };
+ // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+ helpers.requestAnimFrame = (function() {
+ if (typeof window === 'undefined') {
+ return function(callback) {
+ callback();
+ };
+ }
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ }());
+ // -- DOM methods
+ helpers.getRelativePosition = function(evt, chart) {
+ var mouseX, mouseY;
+ var e = evt.originalEvent || evt;
+ var canvas = evt.currentTarget || evt.srcElement;
+ var boundingRect = canvas.getBoundingClientRect();
+
+ var touches = e.touches;
+ if (touches && touches.length > 0) {
+ mouseX = touches[0].clientX;
+ mouseY = touches[0].clientY;
+
+ } else {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ }
+
+ // Scale mouse coordinates into canvas coordinates
+ // by following the pattern laid out by 'jerryj' in the comments of
+ // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
+ var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
+ var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
+ var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
+ var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
+ var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
+ var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
+
+ // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
+ // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
+ mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
+ mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
+
+ return {
+ x: mouseX,
+ y: mouseY
+ };
+
+ };
+
+ // Private helper function to convert max-width/max-height values that may be percentages into a number
+ function parseMaxStyle(styleValue, node, parentProperty) {
+ var valueInPixels;
+ if (typeof styleValue === 'string') {
+ valueInPixels = parseInt(styleValue, 10);
+
+ if (styleValue.indexOf('%') !== -1) {
+ // percentage * size in dimension
+ valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
+ }
+ } else {
+ valueInPixels = styleValue;
+ }
+
+ return valueInPixels;
+ }
+
+ /**
+ * Returns if the given value contains an effective constraint.
+ * @private
+ */
+ function isConstrainedValue(value) {
+ return value !== undefined && value !== null && value !== 'none';
+ }
+
+ // Private helper to get a constraint dimension
+ // @param domNode : the node to check the constraint on
+ // @param maxStyle : the style that defines the maximum for the direction we are using (maxWidth / maxHeight)
+ // @param percentageProperty : property of parent to use when calculating width as a percentage
+ // @see http://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser
+ function getConstraintDimension(domNode, maxStyle, percentageProperty) {
+ var view = document.defaultView;
+ var parentNode = domNode.parentNode;
+ var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
+ var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
+ var hasCNode = isConstrainedValue(constrainedNode);
+ var hasCContainer = isConstrainedValue(constrainedContainer);
+ var infinity = Number.POSITIVE_INFINITY;
+
+ if (hasCNode || hasCContainer) {
+ return Math.min(
+ hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
+ hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
+ }
+
+ return 'none';
+ }
+ // returns Number or undefined if no constraint
+ helpers.getConstraintWidth = function(domNode) {
+ return getConstraintDimension(domNode, 'max-width', 'clientWidth');
+ };
+ // returns Number or undefined if no constraint
+ helpers.getConstraintHeight = function(domNode) {
+ return getConstraintDimension(domNode, 'max-height', 'clientHeight');
+ };
+ helpers.getMaximumWidth = function(domNode) {
+ var container = domNode.parentNode;
+ if (!container) {
+ return domNode.clientWidth;
+ }
+
+ var paddingLeft = parseInt(helpers.getStyle(container, 'padding-left'), 10);
+ var paddingRight = parseInt(helpers.getStyle(container, 'padding-right'), 10);
+ var w = container.clientWidth - paddingLeft - paddingRight;
+ var cw = helpers.getConstraintWidth(domNode);
+ return isNaN(cw) ? w : Math.min(w, cw);
+ };
+ helpers.getMaximumHeight = function(domNode) {
+ var container = domNode.parentNode;
+ if (!container) {
+ return domNode.clientHeight;
+ }
+
+ var paddingTop = parseInt(helpers.getStyle(container, 'padding-top'), 10);
+ var paddingBottom = parseInt(helpers.getStyle(container, 'padding-bottom'), 10);
+ var h = container.clientHeight - paddingTop - paddingBottom;
+ var ch = helpers.getConstraintHeight(domNode);
+ return isNaN(ch) ? h : Math.min(h, ch);
+ };
+ helpers.getStyle = function(el, property) {
+ return el.currentStyle ?
+ el.currentStyle[property] :
+ document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
+ };
+ helpers.retinaScale = function(chart, forceRatio) {
+ var pixelRatio = chart.currentDevicePixelRatio = forceRatio || window.devicePixelRatio || 1;
+ if (pixelRatio === 1) {
+ return;
+ }
+
+ var canvas = chart.canvas;
+ var height = chart.height;
+ var width = chart.width;
+
+ canvas.height = height * pixelRatio;
+ canvas.width = width * pixelRatio;
+ chart.ctx.scale(pixelRatio, pixelRatio);
+
+ // If no style has been set on the canvas, the render size is used as display size,
+ // making the chart visually bigger, so let's enforce it to the "correct" values.
+ // See https://github.com/chartjs/Chart.js/issues/3575
+ canvas.style.height = height + 'px';
+ canvas.style.width = width + 'px';
+ };
+ // -- Canvas methods
+ helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
+ return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
+ };
+ helpers.longestText = function(ctx, font, arrayOfThings, cache) {
+ cache = cache || {};
+ var data = cache.data = cache.data || {};
+ var gc = cache.garbageCollect = cache.garbageCollect || [];
+
+ if (cache.font !== font) {
+ data = cache.data = {};
+ gc = cache.garbageCollect = [];
+ cache.font = font;
+ }
+
+ ctx.font = font;
+ var longest = 0;
+ helpers.each(arrayOfThings, function(thing) {
+ // Undefined strings and arrays should not be measured
+ if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
+ longest = helpers.measureText(ctx, data, gc, longest, thing);
+ } else if (helpers.isArray(thing)) {
+ // if it is an array lets measure each element
+ // to do maybe simplify this function a bit so we can do this more recursively?
+ helpers.each(thing, function(nestedThing) {
+ // Undefined strings and arrays should not be measured
+ if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
+ longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
+ }
+ });
+ }
+ });
+
+ var gcLen = gc.length / 2;
+ if (gcLen > arrayOfThings.length) {
+ for (var i = 0; i < gcLen; i++) {
+ delete data[gc[i]];
+ }
+ gc.splice(0, gcLen);
+ }
+ return longest;
+ };
+ helpers.measureText = function(ctx, data, gc, longest, string) {
+ var textWidth = data[string];
+ if (!textWidth) {
+ textWidth = data[string] = ctx.measureText(string).width;
+ gc.push(string);
+ }
+ if (textWidth > longest) {
+ longest = textWidth;
+ }
+ return longest;
+ };
+ helpers.numberOfLabelLines = function(arrayOfThings) {
+ var numberOfLines = 1;
+ helpers.each(arrayOfThings, function(thing) {
+ if (helpers.isArray(thing)) {
+ if (thing.length > numberOfLines) {
+ numberOfLines = thing.length;
+ }
+ }
+ });
+ return numberOfLines;
+ };
+
+ helpers.color = !color ?
+ function(value) {
+ console.error('Color.js not found!');
+ return value;
+ } :
+ function(value) {
+ /* global CanvasGradient */
+ if (value instanceof CanvasGradient) {
+ value = defaults.global.defaultColor;
+ }
+
+ return color(value);
+ };
+
+ helpers.getHoverColor = function(colorValue) {
+ /* global CanvasPattern */
+ return (colorValue instanceof CanvasPattern) ?
+ colorValue :
+ helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
+ };
+};
+
+},{"25":25,"3":3,"45":45}],28:[function(require,module,exports){
+'use strict';
+
+var helpers = require(45);
+
+/**
+ * Helper function to get relative position for an event
+ * @param {Event|IEvent} event - The event to get the position for
+ * @param {Chart} chart - The chart
+ * @returns {Point} the event position
+ */
+function getRelativePosition(e, chart) {
+ if (e.native) {
+ return {
+ x: e.x,
+ y: e.y
+ };
+ }
+
+ return helpers.getRelativePosition(e, chart);
+}
+
+/**
+ * Helper function to traverse all of the visible elements in the chart
+ * @param chart {chart} the chart
+ * @param handler {Function} the callback to execute for each visible item
+ */
+function parseVisibleItems(chart, handler) {
+ var datasets = chart.data.datasets;
+ var meta, i, j, ilen, jlen;
+
+ for (i = 0, ilen = datasets.length; i < ilen; ++i) {
+ if (!chart.isDatasetVisible(i)) {
+ continue;
+ }
+
+ meta = chart.getDatasetMeta(i);
+ for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
+ var element = meta.data[j];
+ if (!element._view.skip) {
+ handler(element);
+ }
+ }
+ }
+}
+
+/**
+ * Helper function to get the items that intersect the event position
+ * @param items {ChartElement[]} elements to filter
+ * @param position {Point} the point to be nearest to
+ * @return {ChartElement[]} the nearest items
+ */
+function getIntersectItems(chart, position) {
+ var elements = [];
+
+ parseVisibleItems(chart, function(element) {
+ if (element.inRange(position.x, position.y)) {
+ elements.push(element);
+ }
+ });
+
+ return elements;
+}
+
+/**
+ * Helper function to get the items nearest to the event position considering all visible items in teh chart
+ * @param chart {Chart} the chart to look at elements from
+ * @param position {Point} the point to be nearest to
+ * @param intersect {Boolean} if true, only consider items that intersect the position
+ * @param distanceMetric {Function} function to provide the distance between points
+ * @return {ChartElement[]} the nearest items
+ */
+function getNearestItems(chart, position, intersect, distanceMetric) {
+ var minDistance = Number.POSITIVE_INFINITY;
+ var nearestItems = [];
+
+ parseVisibleItems(chart, function(element) {
+ if (intersect && !element.inRange(position.x, position.y)) {
+ return;
+ }
+
+ var center = element.getCenterPoint();
+ var distance = distanceMetric(position, center);
+
+ if (distance < minDistance) {
+ nearestItems = [element];
+ minDistance = distance;
+ } else if (distance === minDistance) {
+ // Can have multiple items at the same distance in which case we sort by size
+ nearestItems.push(element);
+ }
+ });
+
+ return nearestItems;
+}
+
+/**
+ * Get a distance metric function for two points based on the
+ * axis mode setting
+ * @param {String} axis the axis mode. x|y|xy
+ */
+function getDistanceMetricForAxis(axis) {
+ var useX = axis.indexOf('x') !== -1;
+ var useY = axis.indexOf('y') !== -1;
+
+ return function(pt1, pt2) {
+ var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
+ var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
+ return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
+ };
+}
+
+function indexMode(chart, e, options) {
+ var position = getRelativePosition(e, chart);
+ // Default axis for index mode is 'x' to match old behaviour
+ options.axis = options.axis || 'x';
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
+ var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
+ var elements = [];
+
+ if (!items.length) {
+ return [];
+ }
+
+ chart.data.datasets.forEach(function(dataset, datasetIndex) {
+ if (chart.isDatasetVisible(datasetIndex)) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ var element = meta.data[items[0]._index];
+
+ // don't count items that are skipped (null data)
+ if (element && !element._view.skip) {
+ elements.push(element);
+ }
+ }
+ });
+
+ return elements;
+}
+
+/**
+ * @interface IInteractionOptions
+ */
+/**
+ * If true, only consider items that intersect the point
+ * @name IInterfaceOptions#boolean
+ * @type Boolean
+ */
+
+/**
+ * Contains interaction related functions
+ * @namespace Chart.Interaction
+ */
+module.exports = {
+ // Helper function for different modes
+ modes: {
+ single: function(chart, e) {
+ var position = getRelativePosition(e, chart);
+ var elements = [];
+
+ parseVisibleItems(chart, function(element) {
+ if (element.inRange(position.x, position.y)) {
+ elements.push(element);
+ return elements;
+ }
+ });
+
+ return elements.slice(0, 1);
+ },
+
+ /**
+ * @function Chart.Interaction.modes.label
+ * @deprecated since version 2.4.0
+ * @todo remove at version 3
+ * @private
+ */
+ label: indexMode,
+
+ /**
+ * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
+ * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
+ * @function Chart.Interaction.modes.index
+ * @since v2.4.0
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use during interaction
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ index: indexMode,
+
+ /**
+ * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
+ * If the options.intersect is false, we find the nearest item and return the items in that dataset
+ * @function Chart.Interaction.modes.dataset
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use during interaction
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ dataset: function(chart, e, options) {
+ var position = getRelativePosition(e, chart);
+ options.axis = options.axis || 'xy';
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
+ var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
+
+ if (items.length > 0) {
+ items = chart.getDatasetMeta(items[0]._datasetIndex).data;
+ }
+
+ return items;
+ },
+
+ /**
+ * @function Chart.Interaction.modes.x-axis
+ * @deprecated since version 2.4.0. Use index mode and intersect == true
+ * @todo remove at version 3
+ * @private
+ */
+ 'x-axis': function(chart, e) {
+ return indexMode(chart, e, {intersect: false});
+ },
+
+ /**
+ * Point mode returns all elements that hit test based on the event position
+ * of the event
+ * @function Chart.Interaction.modes.intersect
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ point: function(chart, e) {
+ var position = getRelativePosition(e, chart);
+ return getIntersectItems(chart, position);
+ },
+
+ /**
+ * nearest mode returns the element closest to the point
+ * @function Chart.Interaction.modes.intersect
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ nearest: function(chart, e, options) {
+ var position = getRelativePosition(e, chart);
+ options.axis = options.axis || 'xy';
+ var distanceMetric = getDistanceMetricForAxis(options.axis);
+ var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric);
+
+ // We have multiple items at the same distance from the event. Now sort by smallest
+ if (nearestItems.length > 1) {
+ nearestItems.sort(function(a, b) {
+ var sizeA = a.getArea();
+ var sizeB = b.getArea();
+ var ret = sizeA - sizeB;
+
+ if (ret === 0) {
+ // if equal sort by dataset index
+ ret = a._datasetIndex - b._datasetIndex;
+ }
+
+ return ret;
+ });
+ }
+
+ // Return only 1 item
+ return nearestItems.slice(0, 1);
+ },
+
+ /**
+ * x mode returns the elements that hit-test at the current x coordinate
+ * @function Chart.Interaction.modes.x
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ x: function(chart, e, options) {
+ var position = getRelativePosition(e, chart);
+ var items = [];
+ var intersectsItem = false;
+
+ parseVisibleItems(chart, function(element) {
+ if (element.inXRange(position.x)) {
+ items.push(element);
+ }
+
+ if (element.inRange(position.x, position.y)) {
+ intersectsItem = true;
+ }
+ });
+
+ // If we want to trigger on an intersect and we don't have any items
+ // that intersect the position, return nothing
+ if (options.intersect && !intersectsItem) {
+ items = [];
+ }
+ return items;
+ },
+
+ /**
+ * y mode returns the elements that hit-test at the current y coordinate
+ * @function Chart.Interaction.modes.y
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ y: function(chart, e, options) {
+ var position = getRelativePosition(e, chart);
+ var items = [];
+ var intersectsItem = false;
+
+ parseVisibleItems(chart, function(element) {
+ if (element.inYRange(position.y)) {
+ items.push(element);
+ }
+
+ if (element.inRange(position.x, position.y)) {
+ intersectsItem = true;
+ }
+ });
+
+ // If we want to trigger on an intersect and we don't have any items
+ // that intersect the position, return nothing
+ if (options.intersect && !intersectsItem) {
+ items = [];
+ }
+ return items;
+ }
+ }
+};
+
+},{"45":45}],29:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+
+defaults._set('global', {
+ responsive: true,
+ responsiveAnimationDuration: 0,
+ maintainAspectRatio: true,
+ events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
+ hover: {
+ onHover: null,
+ mode: 'nearest',
+ intersect: true,
+ animationDuration: 400
+ },
+ onClick: null,
+ defaultColor: 'rgba(0,0,0,0.1)',
+ defaultFontColor: '#666',
+ defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+ defaultFontSize: 12,
+ defaultFontStyle: 'normal',
+ showLines: true,
+
+ // Element defaults defined in element extensions
+ elements: {},
+
+ // Layout options such as padding
+ layout: {
+ padding: {
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0
+ }
+ }
+});
+
+module.exports = function() {
+
+ // Occupy the global variable of Chart, and create a simple base class
+ var Chart = function(item, config) {
+ this.construct(item, config);
+ return this;
+ };
+
+ Chart.Chart = Chart;
+
+ return Chart;
+};
+
+},{"25":25}],30:[function(require,module,exports){
+'use strict';
+
+var helpers = require(45);
+
+module.exports = function(Chart) {
+
+ function filterByPosition(array, position) {
+ return helpers.where(array, function(v) {
+ return v.position === position;
+ });
+ }
+
+ function sortByWeight(array, reverse) {
+ array.forEach(function(v, i) {
+ v._tmpIndex_ = i;
+ return v;
+ });
+ array.sort(function(a, b) {
+ var v0 = reverse ? b : a;
+ var v1 = reverse ? a : b;
+ return v0.weight === v1.weight ?
+ v0._tmpIndex_ - v1._tmpIndex_ :
+ v0.weight - v1.weight;
+ });
+ array.forEach(function(v) {
+ delete v._tmpIndex_;
+ });
+ }
+
+ /**
+ * @interface ILayoutItem
+ * @prop {String} position - The position of the item in the chart layout. Possible values are
+ * 'left', 'top', 'right', 'bottom', and 'chartArea'
+ * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
+ * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
+ * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
+ * @prop {Function} update - Takes two parameters: width and height. Returns size of item
+ * @prop {Function} getPadding - Returns an object with padding on the edges
+ * @prop {Number} width - Width of item. Must be valid after update()
+ * @prop {Number} height - Height of item. Must be valid after update()
+ * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
+ * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
+ * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
+ * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
+ */
+
+ // The layout service is very self explanatory. It's responsible for the layout within a chart.
+ // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
+ // It is this service's responsibility of carrying out that layout.
+ Chart.layoutService = {
+ defaults: {},
+
+ /**
+ * Register a box to a chart.
+ * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
+ * @param {Chart} chart - the chart to use
+ * @param {ILayoutItem} item - the item to add to be layed out
+ */
+ addBox: function(chart, item) {
+ if (!chart.boxes) {
+ chart.boxes = [];
+ }
+
+ // initialize item with default values
+ item.fullWidth = item.fullWidth || false;
+ item.position = item.position || 'top';
+ item.weight = item.weight || 0;
+
+ chart.boxes.push(item);
+ },
+
+ /**
+ * Remove a layoutItem from a chart
+ * @param {Chart} chart - the chart to remove the box from
+ * @param {Object} layoutItem - the item to remove from the layout
+ */
+ removeBox: function(chart, layoutItem) {
+ var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
+ if (index !== -1) {
+ chart.boxes.splice(index, 1);
+ }
+ },
+
+ /**
+ * Sets (or updates) options on the given `item`.
+ * @param {Chart} chart - the chart in which the item lives (or will be added to)
+ * @param {Object} item - the item to configure with the given options
+ * @param {Object} options - the new item options.
+ */
+ configure: function(chart, item, options) {
+ var props = ['fullWidth', 'position', 'weight'];
+ var ilen = props.length;
+ var i = 0;
+ var prop;
+
+ for (; i < ilen; ++i) {
+ prop = props[i];
+ if (options.hasOwnProperty(prop)) {
+ item[prop] = options[prop];
+ }
+ }
+ },
+
+ /**
+ * Fits boxes of the given chart into the given size by having each box measure itself
+ * then running a fitting algorithm
+ * @param {Chart} chart - the chart
+ * @param {Number} width - the width to fit into
+ * @param {Number} height - the height to fit into
+ */
+ update: function(chart, width, height) {
+ if (!chart) {
+ return;
+ }
+
+ var layoutOptions = chart.options.layout || {};
+ var padding = helpers.options.toPadding(layoutOptions.padding);
+ var leftPadding = padding.left;
+ var rightPadding = padding.right;
+ var topPadding = padding.top;
+ var bottomPadding = padding.bottom;
+
+ var leftBoxes = filterByPosition(chart.boxes, 'left');
+ var rightBoxes = filterByPosition(chart.boxes, 'right');
+ var topBoxes = filterByPosition(chart.boxes, 'top');
+ var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
+ var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
+
+ // Sort boxes by weight. A higher weight is further away from the chart area
+ sortByWeight(leftBoxes, true);
+ sortByWeight(rightBoxes, false);
+ sortByWeight(topBoxes, true);
+ sortByWeight(bottomBoxes, false);
+
+ // Essentially we now have any number of boxes on each of the 4 sides.
+ // Our canvas looks like the following.
+ // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
+ // B1 is the bottom axis
+ // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
+ // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
+ // an error will be thrown.
+ //
+ // |----------------------------------------------------|
+ // | T1 (Full Width) |
+ // |----------------------------------------------------|
+ // | | | T2 | |
+ // | |----|-------------------------------------|----|
+ // | | | C1 | | C2 | |
+ // | | |----| |----| |
+ // | | | | |
+ // | L1 | L2 | ChartArea (C0) | R1 |
+ // | | | | |
+ // | | |----| |----| |
+ // | | | C3 | | C4 | |
+ // | |----|-------------------------------------|----|
+ // | | | B1 | |
+ // |----------------------------------------------------|
+ // | B2 (Full Width) |
+ // |----------------------------------------------------|
+ //
+ // What we do to find the best sizing, we do the following
+ // 1. Determine the minimum size of the chart area.
+ // 2. Split the remaining width equally between each vertical axis
+ // 3. Split the remaining height equally between each horizontal axis
+ // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
+ // 5. Adjust the sizes of each axis based on it's minimum reported size.
+ // 6. Refit each axis
+ // 7. Position each axis in the final location
+ // 8. Tell the chart the final location of the chart area
+ // 9. Tell any axes that overlay the chart area the positions of the chart area
+
+ // Step 1
+ var chartWidth = width - leftPadding - rightPadding;
+ var chartHeight = height - topPadding - bottomPadding;
+ var chartAreaWidth = chartWidth / 2; // min 50%
+ var chartAreaHeight = chartHeight / 2; // min 50%
+
+ // Step 2
+ var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length);
+
+ // Step 3
+ var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length);
+
+ // Step 4
+ var maxChartAreaWidth = chartWidth;
+ var maxChartAreaHeight = chartHeight;
+ var minBoxSizes = [];
+
+ function getMinimumBoxSize(box) {
+ var minSize;
+ var isHorizontal = box.isHorizontal();
+
+ if (isHorizontal) {
+ minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
+ maxChartAreaHeight -= minSize.height;
+ } else {
+ minSize = box.update(verticalBoxWidth, chartAreaHeight);
+ maxChartAreaWidth -= minSize.width;
+ }
+
+ minBoxSizes.push({
+ horizontal: isHorizontal,
+ minSize: minSize,
+ box: box,
+ });
+ }
+
+ helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize);
+
+ // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
+ var maxHorizontalLeftPadding = 0;
+ var maxHorizontalRightPadding = 0;
+ var maxVerticalTopPadding = 0;
+ var maxVerticalBottomPadding = 0;
+
+ helpers.each(topBoxes.concat(bottomBoxes), function(horizontalBox) {
+ if (horizontalBox.getPadding) {
+ var boxPadding = horizontalBox.getPadding();
+ maxHorizontalLeftPadding = Math.max(maxHorizontalLeftPadding, boxPadding.left);
+ maxHorizontalRightPadding = Math.max(maxHorizontalRightPadding, boxPadding.right);
+ }
+ });
+
+ helpers.each(leftBoxes.concat(rightBoxes), function(verticalBox) {
+ if (verticalBox.getPadding) {
+ var boxPadding = verticalBox.getPadding();
+ maxVerticalTopPadding = Math.max(maxVerticalTopPadding, boxPadding.top);
+ maxVerticalBottomPadding = Math.max(maxVerticalBottomPadding, boxPadding.bottom);
+ }
+ });
+
+ // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
+ // be if the axes are drawn at their minimum sizes.
+ // Steps 5 & 6
+ var totalLeftBoxesWidth = leftPadding;
+ var totalRightBoxesWidth = rightPadding;
+ var totalTopBoxesHeight = topPadding;
+ var totalBottomBoxesHeight = bottomPadding;
+
+ // Function to fit a box
+ function fitBox(box) {
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) {
+ return minBox.box === box;
+ });
+
+ if (minBoxSize) {
+ if (box.isHorizontal()) {
+ var scaleMargin = {
+ left: Math.max(totalLeftBoxesWidth, maxHorizontalLeftPadding),
+ right: Math.max(totalRightBoxesWidth, maxHorizontalRightPadding),
+ top: 0,
+ bottom: 0
+ };
+
+ // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
+ // on the margin. Sometimes they need to increase in size slightly
+ box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
+ } else {
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight);
+ }
+ }
+ }
+
+ // Update, and calculate the left and right margins for the horizontal boxes
+ helpers.each(leftBoxes.concat(rightBoxes), fitBox);
+
+ helpers.each(leftBoxes, function(box) {
+ totalLeftBoxesWidth += box.width;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ totalRightBoxesWidth += box.width;
+ });
+
+ // Set the Left and Right margins for the horizontal boxes
+ helpers.each(topBoxes.concat(bottomBoxes), fitBox);
+
+ // Figure out how much margin is on the top and bottom of the vertical boxes
+ helpers.each(topBoxes, function(box) {
+ totalTopBoxesHeight += box.height;
+ });
+
+ helpers.each(bottomBoxes, function(box) {
+ totalBottomBoxesHeight += box.height;
+ });
+
+ function finalFitVerticalBox(box) {
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) {
+ return minSize.box === box;
+ });
+
+ var scaleMargin = {
+ left: 0,
+ right: 0,
+ top: totalTopBoxesHeight,
+ bottom: totalBottomBoxesHeight
+ };
+
+ if (minBoxSize) {
+ box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin);
+ }
+ }
+
+ // Let the left layout know the final margin
+ helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox);
+
+ // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
+ totalLeftBoxesWidth = leftPadding;
+ totalRightBoxesWidth = rightPadding;
+ totalTopBoxesHeight = topPadding;
+ totalBottomBoxesHeight = bottomPadding;
+
+ helpers.each(leftBoxes, function(box) {
+ totalLeftBoxesWidth += box.width;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ totalRightBoxesWidth += box.width;
+ });
+
+ helpers.each(topBoxes, function(box) {
+ totalTopBoxesHeight += box.height;
+ });
+ helpers.each(bottomBoxes, function(box) {
+ totalBottomBoxesHeight += box.height;
+ });
+
+ // We may be adding some padding to account for rotated x axis labels
+ var leftPaddingAddition = Math.max(maxHorizontalLeftPadding - totalLeftBoxesWidth, 0);
+ totalLeftBoxesWidth += leftPaddingAddition;
+ totalRightBoxesWidth += Math.max(maxHorizontalRightPadding - totalRightBoxesWidth, 0);
+
+ var topPaddingAddition = Math.max(maxVerticalTopPadding - totalTopBoxesHeight, 0);
+ totalTopBoxesHeight += topPaddingAddition;
+ totalBottomBoxesHeight += Math.max(maxVerticalBottomPadding - totalBottomBoxesHeight, 0);
+
+ // Figure out if our chart area changed. This would occur if the dataset layout label rotation
+ // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
+ // without calling `fit` again
+ var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight;
+ var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth;
+
+ if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
+ helpers.each(leftBoxes, function(box) {
+ box.height = newMaxChartAreaHeight;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ box.height = newMaxChartAreaHeight;
+ });
+
+ helpers.each(topBoxes, function(box) {
+ if (!box.fullWidth) {
+ box.width = newMaxChartAreaWidth;
+ }
+ });
+
+ helpers.each(bottomBoxes, function(box) {
+ if (!box.fullWidth) {
+ box.width = newMaxChartAreaWidth;
+ }
+ });
+
+ maxChartAreaHeight = newMaxChartAreaHeight;
+ maxChartAreaWidth = newMaxChartAreaWidth;
+ }
+
+ // Step 7 - Position the boxes
+ var left = leftPadding + leftPaddingAddition;
+ var top = topPadding + topPaddingAddition;
+
+ function placeBox(box) {
+ if (box.isHorizontal()) {
+ box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
+ box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
+ box.top = top;
+ box.bottom = top + box.height;
+
+ // Move to next point
+ top = box.bottom;
+
+ } else {
+
+ box.left = left;
+ box.right = left + box.width;
+ box.top = totalTopBoxesHeight;
+ box.bottom = totalTopBoxesHeight + maxChartAreaHeight;
+
+ // Move to next point
+ left = box.right;
+ }
+ }
+
+ helpers.each(leftBoxes.concat(topBoxes), placeBox);
+
+ // Account for chart width and height
+ left += maxChartAreaWidth;
+ top += maxChartAreaHeight;
+
+ helpers.each(rightBoxes, placeBox);
+ helpers.each(bottomBoxes, placeBox);
+
+ // Step 8
+ chart.chartArea = {
+ left: totalLeftBoxesWidth,
+ top: totalTopBoxesHeight,
+ right: totalLeftBoxesWidth + maxChartAreaWidth,
+ bottom: totalTopBoxesHeight + maxChartAreaHeight
+ };
+
+ // Step 9
+ helpers.each(chartAreaBoxes, function(box) {
+ box.left = chart.chartArea.left;
+ box.top = chart.chartArea.top;
+ box.right = chart.chartArea.right;
+ box.bottom = chart.chartArea.bottom;
+
+ box.update(maxChartAreaWidth, maxChartAreaHeight);
+ });
+ }
+ };
+};
+
+},{"45":45}],31:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+
+defaults._set('global', {
+ plugins: {}
+});
+
+module.exports = function(Chart) {
+
+ /**
+ * The plugin service singleton
+ * @namespace Chart.plugins
+ * @since 2.1.0
+ */
+ Chart.plugins = {
+ /**
+ * Globally registered plugins.
+ * @private
+ */
+ _plugins: [],
+
+ /**
+ * This identifier is used to invalidate the descriptors cache attached to each chart
+ * when a global plugin is registered or unregistered. In this case, the cache ID is
+ * incremented and descriptors are regenerated during following API calls.
+ * @private
+ */
+ _cacheId: 0,
+
+ /**
+ * Registers the given plugin(s) if not already registered.
+ * @param {Array|Object} plugins plugin instance(s).
+ */
+ register: function(plugins) {
+ var p = this._plugins;
+ ([]).concat(plugins).forEach(function(plugin) {
+ if (p.indexOf(plugin) === -1) {
+ p.push(plugin);
+ }
+ });
+
+ this._cacheId++;
+ },
+
+ /**
+ * Unregisters the given plugin(s) only if registered.
+ * @param {Array|Object} plugins plugin instance(s).
+ */
+ unregister: function(plugins) {
+ var p = this._plugins;
+ ([]).concat(plugins).forEach(function(plugin) {
+ var idx = p.indexOf(plugin);
+ if (idx !== -1) {
+ p.splice(idx, 1);
+ }
+ });
+
+ this._cacheId++;
+ },
+
+ /**
+ * Remove all registered plugins.
+ * @since 2.1.5
+ */
+ clear: function() {
+ this._plugins = [];
+ this._cacheId++;
+ },
+
+ /**
+ * Returns the number of registered plugins?
+ * @returns {Number}
+ * @since 2.1.5
+ */
+ count: function() {
+ return this._plugins.length;
+ },
+
+ /**
+ * Returns all registered plugin instances.
+ * @returns {Array} array of plugin objects.
+ * @since 2.1.5
+ */
+ getAll: function() {
+ return this._plugins;
+ },
+
+ /**
+ * Calls enabled plugins for `chart` on the specified hook and with the given args.
+ * This method immediately returns as soon as a plugin explicitly returns false. The
+ * returned value can be used, for instance, to interrupt the current action.
+ * @param {Object} chart - The chart instance for which plugins should be called.
+ * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
+ * @param {Array} [args] - Extra arguments to apply to the hook call.
+ * @returns {Boolean} false if any of the plugins return false, else returns true.
+ */
+ notify: function(chart, hook, args) {
+ var descriptors = this.descriptors(chart);
+ var ilen = descriptors.length;
+ var i, descriptor, plugin, params, method;
+
+ for (i = 0; i < ilen; ++i) {
+ descriptor = descriptors[i];
+ plugin = descriptor.plugin;
+ method = plugin[hook];
+ if (typeof method === 'function') {
+ params = [chart].concat(args || []);
+ params.push(descriptor.options);
+ if (method.apply(plugin, params) === false) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Returns descriptors of enabled plugins for the given chart.
+ * @returns {Array} [{ plugin, options }]
+ * @private
+ */
+ descriptors: function(chart) {
+ var cache = chart._plugins || (chart._plugins = {});
+ if (cache.id === this._cacheId) {
+ return cache.descriptors;
+ }
+
+ var plugins = [];
+ var descriptors = [];
+ var config = (chart && chart.config) || {};
+ var options = (config.options && config.options.plugins) || {};
+
+ this._plugins.concat(config.plugins || []).forEach(function(plugin) {
+ var idx = plugins.indexOf(plugin);
+ if (idx !== -1) {
+ return;
+ }
+
+ var id = plugin.id;
+ var opts = options[id];
+ if (opts === false) {
+ return;
+ }
+
+ if (opts === true) {
+ opts = helpers.clone(defaults.global.plugins[id]);
+ }
+
+ plugins.push(plugin);
+ descriptors.push({
+ plugin: plugin,
+ options: opts || {}
+ });
+ });
+
+ cache.descriptors = descriptors;
+ cache.id = this._cacheId;
+ return descriptors;
+ }
+ };
+
+ /**
+ * Plugin extension hooks.
+ * @interface IPlugin
+ * @since 2.1.0
+ */
+ /**
+ * @method IPlugin#beforeInit
+ * @desc Called before initializing `chart`.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#afterInit
+ * @desc Called after `chart` has been initialized and before the first update.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeUpdate
+ * @desc Called before updating `chart`. If any plugin returns `false`, the update
+ * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart update.
+ */
+ /**
+ * @method IPlugin#afterUpdate
+ * @desc Called after `chart` has been updated and before rendering. Note that this
+ * hook will not be called if the chart update has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeDatasetsUpdate
+ * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
+ * the datasets update is cancelled until another `update` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} false to cancel the datasets update.
+ * @since version 2.1.5
+ */
+ /**
+ * @method IPlugin#afterDatasetsUpdate
+ * @desc Called after the `chart` datasets have been updated. Note that this hook
+ * will not be called if the datasets update has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @since version 2.1.5
+ */
+ /**
+ * @method IPlugin#beforeDatasetUpdate
+ * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
+ * returns `false`, the datasets update is cancelled until another `update` is triggered.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Number} args.index - The dataset index.
+ * @param {Object} args.meta - The dataset metadata.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
+ */
+ /**
+ * @method IPlugin#afterDatasetUpdate
+ * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
+ * that this hook will not be called if the datasets update has been previously cancelled.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Number} args.index - The dataset index.
+ * @param {Object} args.meta - The dataset metadata.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeLayout
+ * @desc Called before laying out `chart`. If any plugin returns `false`,
+ * the layout update is cancelled until another `update` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart layout.
+ */
+ /**
+ * @method IPlugin#afterLayout
+ * @desc Called after the `chart` has been layed out. Note that this hook will not
+ * be called if the layout update has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeRender
+ * @desc Called before rendering `chart`. If any plugin returns `false`,
+ * the rendering is cancelled until another `render` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart rendering.
+ */
+ /**
+ * @method IPlugin#afterRender
+ * @desc Called after the `chart` has been fully rendered (and animation completed). Note
+ * that this hook will not be called if the rendering has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeDraw
+ * @desc Called before drawing `chart` at every animation frame specified by the given
+ * easing value. If any plugin returns `false`, the frame drawing is cancelled until
+ * another `render` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart drawing.
+ */
+ /**
+ * @method IPlugin#afterDraw
+ * @desc Called after the `chart` has been drawn for the specific easing value. Note
+ * that this hook will not be called if the drawing has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeDatasetsDraw
+ * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
+ * the datasets drawing is cancelled until another `render` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
+ */
+ /**
+ * @method IPlugin#afterDatasetsDraw
+ * @desc Called after the `chart` datasets have been drawn. Note that this hook
+ * will not be called if the datasets drawing has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeDatasetDraw
+ * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
+ * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
+ * is cancelled until another `render` is triggered.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Number} args.index - The dataset index.
+ * @param {Object} args.meta - The dataset metadata.
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
+ */
+ /**
+ * @method IPlugin#afterDatasetDraw
+ * @desc Called after the `chart` datasets at the given `args.index` have been drawn
+ * (datasets are drawn in the reverse order). Note that this hook will not be called
+ * if the datasets drawing has been previously cancelled.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Number} args.index - The dataset index.
+ * @param {Object} args.meta - The dataset metadata.
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeTooltipDraw
+ * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
+ * the tooltip drawing is cancelled until another `render` is triggered.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Object} args.tooltip - The tooltip.
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart tooltip drawing.
+ */
+ /**
+ * @method IPlugin#afterTooltipDraw
+ * @desc Called after drawing the `tooltip`. Note that this hook will not
+ * be called if the tooltip drawing has been previously cancelled.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Object} args.tooltip - The tooltip.
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#beforeEvent
+ * @desc Called before processing the specified `event`. If any plugin returns `false`,
+ * the event will be discarded.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {IEvent} event - The event object.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#afterEvent
+ * @desc Called after the `event` has been consumed. Note that this hook
+ * will not be called if the `event` has been previously discarded.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {IEvent} event - The event object.
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#resize
+ * @desc Called after the chart as been resized.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
+ * @param {Object} options - The plugin options.
+ */
+ /**
+ * @method IPlugin#destroy
+ * @desc Called after the chart as been destroyed.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+
+ /**
+ * Provided for backward compatibility, use Chart.plugins instead
+ * @namespace Chart.pluginService
+ * @deprecated since version 2.1.5
+ * @todo remove at version 3
+ * @private
+ */
+ Chart.pluginService = Chart.plugins;
+
+ /**
+ * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
+ * effect, instead simply create/register plugins via plain JavaScript objects.
+ * @interface Chart.PluginBase
+ * @deprecated since version 2.5.0
+ * @todo remove at version 3
+ * @private
+ */
+ Chart.PluginBase = Element.extend({});
+};
+
+},{"25":25,"26":26,"45":45}],32:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+var Ticks = require(34);
+
+defaults._set('scale', {
+ display: true,
+ position: 'left',
+ offset: false,
+
+ // grid line settings
+ gridLines: {
+ display: true,
+ color: 'rgba(0, 0, 0, 0.1)',
+ lineWidth: 1,
+ drawBorder: true,
+ drawOnChartArea: true,
+ drawTicks: true,
+ tickMarkLength: 10,
+ zeroLineWidth: 1,
+ zeroLineColor: 'rgba(0,0,0,0.25)',
+ zeroLineBorderDash: [],
+ zeroLineBorderDashOffset: 0.0,
+ offsetGridLines: false,
+ borderDash: [],
+ borderDashOffset: 0.0
+ },
+
+ // scale label
+ scaleLabel: {
+ // display property
+ display: false,
+
+ // actual label
+ labelString: '',
+
+ // line height
+ lineHeight: 1.2,
+
+ // top/bottom padding
+ padding: {
+ top: 4,
+ bottom: 4
+ }
+ },
+
+ // label settings
+ ticks: {
+ beginAtZero: false,
+ minRotation: 0,
+ maxRotation: 50,
+ mirror: false,
+ padding: 0,
+ reverse: false,
+ display: true,
+ autoSkip: true,
+ autoSkipPadding: 0,
+ labelOffset: 0,
+ // We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
+ callback: Ticks.formatters.values,
+ minor: {},
+ major: {}
+ }
+});
+
+function labelsFromTicks(ticks) {
+ var labels = [];
+ var i, ilen;
+
+ for (i = 0, ilen = ticks.length; i < ilen; ++i) {
+ labels.push(ticks[i].label);
+ }
+
+ return labels;
+}
+
+function getLineValue(scale, index, offsetGridLines) {
+ var lineValue = scale.getPixelForTick(index);
+
+ if (offsetGridLines) {
+ if (index === 0) {
+ lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
+ } else {
+ lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
+ }
+ }
+ return lineValue;
+}
+
+module.exports = function(Chart) {
+
+ function computeTextSize(context, tick, font) {
+ return helpers.isArray(tick) ?
+ helpers.longestText(context, font, tick) :
+ context.measureText(tick).width;
+ }
+
+ function parseFontOptions(options) {
+ var valueOrDefault = helpers.valueOrDefault;
+ var globalDefaults = defaults.global;
+ var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
+ var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle);
+ var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily);
+
+ return {
+ size: size,
+ style: style,
+ family: family,
+ font: helpers.fontString(size, style, family)
+ };
+ }
+
+ function parseLineHeight(options) {
+ return helpers.options.toLineHeight(
+ helpers.valueOrDefault(options.lineHeight, 1.2),
+ helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize));
+ }
+
+ Chart.Scale = Element.extend({
+ /**
+ * Get the padding needed for the scale
+ * @method getPadding
+ * @private
+ * @returns {Padding} the necessary padding
+ */
+ getPadding: function() {
+ var me = this;
+ return {
+ left: me.paddingLeft || 0,
+ top: me.paddingTop || 0,
+ right: me.paddingRight || 0,
+ bottom: me.paddingBottom || 0
+ };
+ },
+
+ /**
+ * Returns the scale tick objects ({label, major})
+ * @since 2.7
+ */
+ getTicks: function() {
+ return this._ticks;
+ },
+
+ // These methods are ordered by lifecyle. Utilities then follow.
+ // Any function defined here is inherited by all scale types.
+ // Any function can be extended by the scale type
+
+ mergeTicksOptions: function() {
+ var ticks = this.options.ticks;
+ if (ticks.minor === false) {
+ ticks.minor = {
+ display: false
+ };
+ }
+ if (ticks.major === false) {
+ ticks.major = {
+ display: false
+ };
+ }
+ for (var key in ticks) {
+ if (key !== 'major' && key !== 'minor') {
+ if (typeof ticks.minor[key] === 'undefined') {
+ ticks.minor[key] = ticks[key];
+ }
+ if (typeof ticks.major[key] === 'undefined') {
+ ticks.major[key] = ticks[key];
+ }
+ }
+ }
+ },
+ beforeUpdate: function() {
+ helpers.callback(this.options.beforeUpdate, [this]);
+ },
+ update: function(maxWidth, maxHeight, margins) {
+ var me = this;
+ var i, ilen, labels, label, ticks, tick;
+
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
+ me.beforeUpdate();
+
+ // Absorb the master measurements
+ me.maxWidth = maxWidth;
+ me.maxHeight = maxHeight;
+ me.margins = helpers.extend({
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0
+ }, margins);
+ me.longestTextCache = me.longestTextCache || {};
+
+ // Dimensions
+ me.beforeSetDimensions();
+ me.setDimensions();
+ me.afterSetDimensions();
+
+ // Data min/max
+ me.beforeDataLimits();
+ me.determineDataLimits();
+ me.afterDataLimits();
+
+ // Ticks - `this.ticks` is now DEPRECATED!
+ // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
+ // and must not be accessed directly from outside this class. `this.ticks` being
+ // around for long time and not marked as private, we can't change its structure
+ // without unexpected breaking changes. If you need to access the scale ticks,
+ // use scale.getTicks() instead.
+
+ me.beforeBuildTicks();
+
+ // New implementations should return an array of objects but for BACKWARD COMPAT,
+ // we still support no return (`this.ticks` internally set by calling this method).
+ ticks = me.buildTicks() || [];
+
+ me.afterBuildTicks();
+
+ me.beforeTickToLabelConversion();
+
+ // New implementations should return the formatted tick labels but for BACKWARD
+ // COMPAT, we still support no return (`this.ticks` internally changed by calling
+ // this method and supposed to contain only string values).
+ labels = me.convertTicksToLabels(ticks) || me.ticks;
+
+ me.afterTickToLabelConversion();
+
+ me.ticks = labels; // BACKWARD COMPATIBILITY
+
+ // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
+
+ // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
+ for (i = 0, ilen = labels.length; i < ilen; ++i) {
+ label = labels[i];
+ tick = ticks[i];
+ if (!tick) {
+ ticks.push(tick = {
+ label: label,
+ major: false
+ });
+ } else {
+ tick.label = label;
+ }
+ }
+
+ me._ticks = ticks;
+
+ // Tick Rotation
+ me.beforeCalculateTickRotation();
+ me.calculateTickRotation();
+ me.afterCalculateTickRotation();
+ // Fit
+ me.beforeFit();
+ me.fit();
+ me.afterFit();
+ //
+ me.afterUpdate();
+
+ return me.minSize;
+
+ },
+ afterUpdate: function() {
+ helpers.callback(this.options.afterUpdate, [this]);
+ },
+
+ //
+
+ beforeSetDimensions: function() {
+ helpers.callback(this.options.beforeSetDimensions, [this]);
+ },
+ setDimensions: function() {
+ var me = this;
+ // Set the unconstrained dimension before label rotation
+ if (me.isHorizontal()) {
+ // Reset position before calculating rotation
+ me.width = me.maxWidth;
+ me.left = 0;
+ me.right = me.width;
+ } else {
+ me.height = me.maxHeight;
+
+ // Reset position before calculating rotation
+ me.top = 0;
+ me.bottom = me.height;
+ }
+
+ // Reset padding
+ me.paddingLeft = 0;
+ me.paddingTop = 0;
+ me.paddingRight = 0;
+ me.paddingBottom = 0;
+ },
+ afterSetDimensions: function() {
+ helpers.callback(this.options.afterSetDimensions, [this]);
+ },
+
+ // Data limits
+ beforeDataLimits: function() {
+ helpers.callback(this.options.beforeDataLimits, [this]);
+ },
+ determineDataLimits: helpers.noop,
+ afterDataLimits: function() {
+ helpers.callback(this.options.afterDataLimits, [this]);
+ },
+
+ //
+ beforeBuildTicks: function() {
+ helpers.callback(this.options.beforeBuildTicks, [this]);
+ },
+ buildTicks: helpers.noop,
+ afterBuildTicks: function() {
+ helpers.callback(this.options.afterBuildTicks, [this]);
+ },
+
+ beforeTickToLabelConversion: function() {
+ helpers.callback(this.options.beforeTickToLabelConversion, [this]);
+ },
+ convertTicksToLabels: function() {
+ var me = this;
+ // Convert ticks to strings
+ var tickOpts = me.options.ticks;
+ me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
+ },
+ afterTickToLabelConversion: function() {
+ helpers.callback(this.options.afterTickToLabelConversion, [this]);
+ },
+
+ //
+
+ beforeCalculateTickRotation: function() {
+ helpers.callback(this.options.beforeCalculateTickRotation, [this]);
+ },
+ calculateTickRotation: function() {
+ var me = this;
+ var context = me.ctx;
+ var tickOpts = me.options.ticks;
+ var labels = labelsFromTicks(me._ticks);
+
+ // Get the width of each grid by calculating the difference
+ // between x offsets between 0 and 1.
+ var tickFont = parseFontOptions(tickOpts);
+ context.font = tickFont.font;
+
+ var labelRotation = tickOpts.minRotation || 0;
+
+ if (labels.length && me.options.display && me.isHorizontal()) {
+ var originalLabelWidth = helpers.longestText(context, tickFont.font, labels, me.longestTextCache);
+ var labelWidth = originalLabelWidth;
+ var cosRotation, sinRotation;
+
+ // Allow 3 pixels x2 padding either side for label readability
+ var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
+
+ // Max label rotation can be set or default to 90 - also act as a loop counter
+ while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
+ var angleRadians = helpers.toRadians(labelRotation);
+ cosRotation = Math.cos(angleRadians);
+ sinRotation = Math.sin(angleRadians);
+
+ if (sinRotation * originalLabelWidth > me.maxHeight) {
+ // go back one step
+ labelRotation--;
+ break;
+ }
+
+ labelRotation++;
+ labelWidth = cosRotation * originalLabelWidth;
+ }
+ }
+
+ me.labelRotation = labelRotation;
+ },
+ afterCalculateTickRotation: function() {
+ helpers.callback(this.options.afterCalculateTickRotation, [this]);
+ },
+
+ //
+
+ beforeFit: function() {
+ helpers.callback(this.options.beforeFit, [this]);
+ },
+ fit: function() {
+ var me = this;
+ // Reset
+ var minSize = me.minSize = {
+ width: 0,
+ height: 0
+ };
+
+ var labels = labelsFromTicks(me._ticks);
+
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+ var scaleLabelOpts = opts.scaleLabel;
+ var gridLineOpts = opts.gridLines;
+ var display = opts.display;
+ var isHorizontal = me.isHorizontal();
+
+ var tickFont = parseFontOptions(tickOpts);
+ var tickMarkLength = opts.gridLines.tickMarkLength;
+
+ // Width
+ if (isHorizontal) {
+ // subtract the margins to line up with the chartArea if we are a full width scale
+ minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
+ } else {
+ minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
+ }
+
+ // height
+ if (isHorizontal) {
+ minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
+ } else {
+ minSize.height = me.maxHeight; // fill all the height
+ }
+
+ // Are we showing a title for the scale?
+ if (scaleLabelOpts.display && display) {
+ var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts);
+ var scaleLabelPadding = helpers.options.toPadding(scaleLabelOpts.padding);
+ var deltaHeight = scaleLabelLineHeight + scaleLabelPadding.height;
+
+ if (isHorizontal) {
+ minSize.height += deltaHeight;
+ } else {
+ minSize.width += deltaHeight;
+ }
+ }
+
+ // Don't bother fitting the ticks if we are not showing them
+ if (tickOpts.display && display) {
+ var largestTextWidth = helpers.longestText(me.ctx, tickFont.font, labels, me.longestTextCache);
+ var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels);
+ var lineSpace = tickFont.size * 0.5;
+ var tickPadding = me.options.ticks.padding;
+
+ if (isHorizontal) {
+ // A horizontal axis is more constrained by the height.
+ me.longestLabelWidth = largestTextWidth;
+
+ var angleRadians = helpers.toRadians(me.labelRotation);
+ var cosRotation = Math.cos(angleRadians);
+ var sinRotation = Math.sin(angleRadians);
+
+ // TODO - improve this calculation
+ var labelHeight = (sinRotation * largestTextWidth)
+ + (tickFont.size * tallestLabelHeightInLines)
+ + (lineSpace * (tallestLabelHeightInLines - 1))
+ + lineSpace; // padding
+
+ minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
+
+ me.ctx.font = tickFont.font;
+ var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.font);
+ var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.font);
+
+ // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
+ // which means that the right padding is dominated by the font height
+ if (me.labelRotation !== 0) {
+ me.paddingLeft = opts.position === 'bottom' ? (cosRotation * firstLabelWidth) + 3 : (cosRotation * lineSpace) + 3; // add 3 px to move away from canvas edges
+ me.paddingRight = opts.position === 'bottom' ? (cosRotation * lineSpace) + 3 : (cosRotation * lastLabelWidth) + 3;
+ } else {
+ me.paddingLeft = firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges
+ me.paddingRight = lastLabelWidth / 2 + 3;
+ }
+ } else {
+ // A vertical axis is more constrained by the width. Labels are the
+ // dominant factor here, so get that length first and account for padding
+ if (tickOpts.mirror) {
+ largestTextWidth = 0;
+ } else {
+ // use lineSpace for consistency with horizontal axis
+ // tickPadding is not implemented for horizontal
+ largestTextWidth += tickPadding + lineSpace;
+ }
+
+ minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
+
+ me.paddingTop = tickFont.size / 2;
+ me.paddingBottom = tickFont.size / 2;
+ }
+ }
+
+ me.handleMargins();
+
+ me.width = minSize.width;
+ me.height = minSize.height;
+ },
+
+ /**
+ * Handle margins and padding interactions
+ * @private
+ */
+ handleMargins: function() {
+ var me = this;
+ if (me.margins) {
+ me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
+ me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
+ me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
+ me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
+ }
+ },
+
+ afterFit: function() {
+ helpers.callback(this.options.afterFit, [this]);
+ },
+
+ // Shared Methods
+ isHorizontal: function() {
+ return this.options.position === 'top' || this.options.position === 'bottom';
+ },
+ isFullWidth: function() {
+ return (this.options.fullWidth);
+ },
+
+ // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
+ getRightValue: function(rawValue) {
+ // Null and undefined values first
+ if (helpers.isNullOrUndef(rawValue)) {
+ return NaN;
+ }
+ // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
+ if (typeof rawValue === 'number' && !isFinite(rawValue)) {
+ return NaN;
+ }
+ // If it is in fact an object, dive in one more level
+ if (rawValue) {
+ if (this.isHorizontal()) {
+ if (rawValue.x !== undefined) {
+ return this.getRightValue(rawValue.x);
+ }
+ } else if (rawValue.y !== undefined) {
+ return this.getRightValue(rawValue.y);
+ }
+ }
+
+ // Value is good, return it
+ return rawValue;
+ },
+
+ /**
+ * Used to get the value to display in the tooltip for the data at the given index
+ * @param index
+ * @param datasetIndex
+ */
+ getLabelForIndex: helpers.noop,
+
+ /**
+ * Returns the location of the given data point. Value can either be an index or a numerical value
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
+ * @param value
+ * @param index
+ * @param datasetIndex
+ */
+ getPixelForValue: helpers.noop,
+
+ /**
+ * Used to get the data value from a given pixel. This is the inverse of getPixelForValue
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
+ * @param pixel
+ */
+ getValueForPixel: helpers.noop,
+
+ /**
+ * Returns the location of the tick at the given index
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
+ */
+ getPixelForTick: function(index) {
+ var me = this;
+ var offset = me.options.offset;
+ if (me.isHorizontal()) {
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
+ var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
+ var pixel = (tickWidth * index) + me.paddingLeft;
+
+ if (offset) {
+ pixel += tickWidth / 2;
+ }
+
+ var finalVal = me.left + Math.round(pixel);
+ finalVal += me.isFullWidth() ? me.margins.left : 0;
+ return finalVal;
+ }
+ var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
+ return me.top + (index * (innerHeight / (me._ticks.length - 1)));
+ },
+
+ /**
+ * Utility for getting the pixel location of a percentage of scale
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
+ */
+ getPixelForDecimal: function(decimal) {
+ var me = this;
+ if (me.isHorizontal()) {
+ var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
+ var valueOffset = (innerWidth * decimal) + me.paddingLeft;
+
+ var finalVal = me.left + Math.round(valueOffset);
+ finalVal += me.isFullWidth() ? me.margins.left : 0;
+ return finalVal;
+ }
+ return me.top + (decimal * me.height);
+ },
+
+ /**
+ * Returns the pixel for the minimum chart value
+ * The coordinate (0, 0) is at the upper-left corner of the canvas
+ */
+ getBasePixel: function() {
+ return this.getPixelForValue(this.getBaseValue());
+ },
+
+ getBaseValue: function() {
+ var me = this;
+ var min = me.min;
+ var max = me.max;
+
+ return me.beginAtZero ? 0 :
+ min < 0 && max < 0 ? max :
+ min > 0 && max > 0 ? min :
+ 0;
+ },
+
+ /**
+ * Returns a subset of ticks to be plotted to avoid overlapping labels.
+ * @private
+ */
+ _autoSkip: function(ticks) {
+ var skipRatio;
+ var me = this;
+ var isHorizontal = me.isHorizontal();
+ var optionTicks = me.options.ticks.minor;
+ var tickCount = ticks.length;
+ var labelRotationRadians = helpers.toRadians(me.labelRotation);
+ var cosRotation = Math.cos(labelRotationRadians);
+ var longestRotatedLabel = me.longestLabelWidth * cosRotation;
+ var result = [];
+ var i, tick, shouldSkip;
+
+ // figure out the maximum number of gridlines to show
+ var maxTicks;
+ if (optionTicks.maxTicksLimit) {
+ maxTicks = optionTicks.maxTicksLimit;
+ }
+
+ if (isHorizontal) {
+ skipRatio = false;
+
+ if ((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount > (me.width - (me.paddingLeft + me.paddingRight))) {
+ skipRatio = 1 + Math.floor(((longestRotatedLabel + optionTicks.autoSkipPadding) * tickCount) / (me.width - (me.paddingLeft + me.paddingRight)));
+ }
+
+ // if they defined a max number of optionTicks,
+ // increase skipRatio until that number is met
+ if (maxTicks && tickCount > maxTicks) {
+ skipRatio = Math.max(skipRatio, Math.floor(tickCount / maxTicks));
+ }
+ }
+
+ for (i = 0; i < tickCount; i++) {
+ tick = ticks[i];
+
+ // Since we always show the last tick,we need may need to hide the last shown one before
+ shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount);
+ if (shouldSkip && i !== tickCount - 1) {
+ // leave tick in place but make sure it's not displayed (#4635)
+ delete tick.label;
+ }
+ result.push(tick);
+ }
+ return result;
+ },
+
+ // Actually draw the scale on the canvas
+ // @param {rectangle} chartArea : the area of the chart to draw full grid lines on
+ draw: function(chartArea) {
+ var me = this;
+ var options = me.options;
+ if (!options.display) {
+ return;
+ }
+
+ var context = me.ctx;
+ var globalDefaults = defaults.global;
+ var optionTicks = options.ticks.minor;
+ var optionMajorTicks = options.ticks.major || optionTicks;
+ var gridLines = options.gridLines;
+ var scaleLabel = options.scaleLabel;
+
+ var isRotated = me.labelRotation !== 0;
+ var isHorizontal = me.isHorizontal();
+
+ var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
+ var tickFontColor = helpers.valueOrDefault(optionTicks.fontColor, globalDefaults.defaultFontColor);
+ var tickFont = parseFontOptions(optionTicks);
+ var majorTickFontColor = helpers.valueOrDefault(optionMajorTicks.fontColor, globalDefaults.defaultFontColor);
+ var majorTickFont = parseFontOptions(optionMajorTicks);
+
+ var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
+
+ var scaleLabelFontColor = helpers.valueOrDefault(scaleLabel.fontColor, globalDefaults.defaultFontColor);
+ var scaleLabelFont = parseFontOptions(scaleLabel);
+ var scaleLabelPadding = helpers.options.toPadding(scaleLabel.padding);
+ var labelRotationRadians = helpers.toRadians(me.labelRotation);
+
+ var itemsToDraw = [];
+
+ var xTickStart = options.position === 'right' ? me.left : me.right - tl;
+ var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
+ var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
+ var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
+
+ helpers.each(ticks, function(tick, index) {
+ // autoskipper skipped this tick (#4635)
+ if (helpers.isNullOrUndef(tick.label)) {
+ return;
+ }
+
+ var label = tick.label;
+ var lineWidth, lineColor, borderDash, borderDashOffset;
+ if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
+ // Draw the first index specially
+ lineWidth = gridLines.zeroLineWidth;
+ lineColor = gridLines.zeroLineColor;
+ borderDash = gridLines.zeroLineBorderDash;
+ borderDashOffset = gridLines.zeroLineBorderDashOffset;
+ } else {
+ lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index);
+ lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index);
+ borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash);
+ borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset);
+ }
+
+ // Common properties
+ var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY;
+ var textAlign = 'middle';
+ var textBaseline = 'middle';
+ var tickPadding = optionTicks.padding;
+
+ if (isHorizontal) {
+ var labelYOffset = tl + tickPadding;
+
+ if (options.position === 'bottom') {
+ // bottom
+ textBaseline = !isRotated ? 'top' : 'middle';
+ textAlign = !isRotated ? 'center' : 'right';
+ labelY = me.top + labelYOffset;
+ } else {
+ // top
+ textBaseline = !isRotated ? 'bottom' : 'middle';
+ textAlign = !isRotated ? 'center' : 'left';
+ labelY = me.bottom - labelYOffset;
+ }
+
+ var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
+ if (xLineValue < me.left) {
+ lineColor = 'rgba(0,0,0,0)';
+ }
+ xLineValue += helpers.aliasPixel(lineWidth);
+
+ labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
+
+ tx1 = tx2 = x1 = x2 = xLineValue;
+ ty1 = yTickStart;
+ ty2 = yTickEnd;
+ y1 = chartArea.top;
+ y2 = chartArea.bottom;
+ } else {
+ var isLeft = options.position === 'left';
+ var labelXOffset;
+
+ if (optionTicks.mirror) {
+ textAlign = isLeft ? 'left' : 'right';
+ labelXOffset = tickPadding;
+ } else {
+ textAlign = isLeft ? 'right' : 'left';
+ labelXOffset = tl + tickPadding;
+ }
+
+ labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
+
+ var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
+ if (yLineValue < me.top) {
+ lineColor = 'rgba(0,0,0,0)';
+ }
+ yLineValue += helpers.aliasPixel(lineWidth);
+
+ labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
+
+ tx1 = xTickStart;
+ tx2 = xTickEnd;
+ x1 = chartArea.left;
+ x2 = chartArea.right;
+ ty1 = ty2 = y1 = y2 = yLineValue;
+ }
+
+ itemsToDraw.push({
+ tx1: tx1,
+ ty1: ty1,
+ tx2: tx2,
+ ty2: ty2,
+ x1: x1,
+ y1: y1,
+ x2: x2,
+ y2: y2,
+ labelX: labelX,
+ labelY: labelY,
+ glWidth: lineWidth,
+ glColor: lineColor,
+ glBorderDash: borderDash,
+ glBorderDashOffset: borderDashOffset,
+ rotation: -1 * labelRotationRadians,
+ label: label,
+ major: tick.major,
+ textBaseline: textBaseline,
+ textAlign: textAlign
+ });
+ });
+
+ // Draw all of the tick labels, tick marks, and grid lines at the correct places
+ helpers.each(itemsToDraw, function(itemToDraw) {
+ if (gridLines.display) {
+ context.save();
+ context.lineWidth = itemToDraw.glWidth;
+ context.strokeStyle = itemToDraw.glColor;
+ if (context.setLineDash) {
+ context.setLineDash(itemToDraw.glBorderDash);
+ context.lineDashOffset = itemToDraw.glBorderDashOffset;
+ }
+
+ context.beginPath();
+
+ if (gridLines.drawTicks) {
+ context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
+ context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
+ }
+
+ if (gridLines.drawOnChartArea) {
+ context.moveTo(itemToDraw.x1, itemToDraw.y1);
+ context.lineTo(itemToDraw.x2, itemToDraw.y2);
+ }
+
+ context.stroke();
+ context.restore();
+ }
+
+ if (optionTicks.display) {
+ // Make sure we draw text in the correct color and font
+ context.save();
+ context.translate(itemToDraw.labelX, itemToDraw.labelY);
+ context.rotate(itemToDraw.rotation);
+ context.font = itemToDraw.major ? majorTickFont.font : tickFont.font;
+ context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
+ context.textBaseline = itemToDraw.textBaseline;
+ context.textAlign = itemToDraw.textAlign;
+
+ var label = itemToDraw.label;
+ if (helpers.isArray(label)) {
+ for (var i = 0, y = 0; i < label.length; ++i) {
+ // We just make sure the multiline element is a string here..
+ context.fillText('' + label[i], 0, y);
+ // apply same lineSpacing as calculated @ L#320
+ y += (tickFont.size * 1.5);
+ }
+ } else {
+ context.fillText(label, 0, 0);
+ }
+ context.restore();
+ }
+ });
+
+ if (scaleLabel.display) {
+ // Draw the scale label
+ var scaleLabelX;
+ var scaleLabelY;
+ var rotation = 0;
+ var halfLineHeight = parseLineHeight(scaleLabel) / 2;
+
+ if (isHorizontal) {
+ scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
+ scaleLabelY = options.position === 'bottom'
+ ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
+ : me.top + halfLineHeight + scaleLabelPadding.top;
+ } else {
+ var isLeft = options.position === 'left';
+ scaleLabelX = isLeft
+ ? me.left + halfLineHeight + scaleLabelPadding.top
+ : me.right - halfLineHeight - scaleLabelPadding.top;
+ scaleLabelY = me.top + ((me.bottom - me.top) / 2);
+ rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
+ }
+
+ context.save();
+ context.translate(scaleLabelX, scaleLabelY);
+ context.rotate(rotation);
+ context.textAlign = 'center';
+ context.textBaseline = 'middle';
+ context.fillStyle = scaleLabelFontColor; // render in correct colour
+ context.font = scaleLabelFont.font;
+ context.fillText(scaleLabel.labelString, 0, 0);
+ context.restore();
+ }
+
+ if (gridLines.drawBorder) {
+ // Draw the line at the edge of the axis
+ context.lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, 0);
+ context.strokeStyle = helpers.valueAtIndexOrDefault(gridLines.color, 0);
+ var x1 = me.left;
+ var x2 = me.right;
+ var y1 = me.top;
+ var y2 = me.bottom;
+
+ var aliasPixel = helpers.aliasPixel(context.lineWidth);
+ if (isHorizontal) {
+ y1 = y2 = options.position === 'top' ? me.bottom : me.top;
+ y1 += aliasPixel;
+ y2 += aliasPixel;
+ } else {
+ x1 = x2 = options.position === 'left' ? me.right : me.left;
+ x1 += aliasPixel;
+ x2 += aliasPixel;
+ }
+
+ context.beginPath();
+ context.moveTo(x1, y1);
+ context.lineTo(x2, y2);
+ context.stroke();
+ }
+ }
+ });
+};
+
+},{"25":25,"26":26,"34":34,"45":45}],33:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var helpers = require(45);
+
+module.exports = function(Chart) {
+
+ Chart.scaleService = {
+ // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
+ // use the new chart options to grab the correct scale
+ constructors: {},
+ // Use a registration function so that we can move to an ES6 map when we no longer need to support
+ // old browsers
+
+ // Scale config defaults
+ defaults: {},
+ registerScaleType: function(type, scaleConstructor, scaleDefaults) {
+ this.constructors[type] = scaleConstructor;
+ this.defaults[type] = helpers.clone(scaleDefaults);
+ },
+ getScaleConstructor: function(type) {
+ return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
+ },
+ getScaleDefaults: function(type) {
+ // Return the scale defaults merged with the global settings so that we always use the latest ones
+ return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {};
+ },
+ updateScaleDefaults: function(type, additions) {
+ var me = this;
+ if (me.defaults.hasOwnProperty(type)) {
+ me.defaults[type] = helpers.extend(me.defaults[type], additions);
+ }
+ },
+ addScalesToLayout: function(chart) {
+ // Adds each scale to the chart.boxes array to be sized accordingly
+ helpers.each(chart.scales, function(scale) {
+ // Set ILayoutItem parameters for backwards compatibility
+ scale.fullWidth = scale.options.fullWidth;
+ scale.position = scale.options.position;
+ scale.weight = scale.options.weight;
+ Chart.layoutService.addBox(chart, scale);
+ });
+ }
+ };
+};
+
+},{"25":25,"45":45}],34:[function(require,module,exports){
+'use strict';
+
+var helpers = require(45);
+
+/**
+ * Namespace to hold static tick generation functions
+ * @namespace Chart.Ticks
+ */
+module.exports = {
+ /**
+ * Namespace to hold generators for different types of ticks
+ * @namespace Chart.Ticks.generators
+ */
+ generators: {
+ /**
+ * Interface for the options provided to the numeric tick generator
+ * @interface INumericTickGenerationOptions
+ */
+ /**
+ * The maximum number of ticks to display
+ * @name INumericTickGenerationOptions#maxTicks
+ * @type Number
+ */
+ /**
+ * The distance between each tick.
+ * @name INumericTickGenerationOptions#stepSize
+ * @type Number
+ * @optional
+ */
+ /**
+ * Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum
+ * @name INumericTickGenerationOptions#min
+ * @type Number
+ * @optional
+ */
+ /**
+ * The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum
+ * @name INumericTickGenerationOptions#max
+ * @type Number
+ * @optional
+ */
+
+ /**
+ * Generate a set of linear ticks
+ * @method Chart.Ticks.generators.linear
+ * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
+ * @param dataRange {IRange} the range of the data
+ * @returns {Array} array of tick values
+ */
+ linear: function(generationOptions, dataRange) {
+ var ticks = [];
+ // To get a "nice" value for the tick spacing, we will use the appropriately named
+ // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
+ // for details.
+
+ var spacing;
+ if (generationOptions.stepSize && generationOptions.stepSize > 0) {
+ spacing = generationOptions.stepSize;
+ } else {
+ var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
+ spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
+ }
+ var niceMin = Math.floor(dataRange.min / spacing) * spacing;
+ var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
+
+ // If min, max and stepSize is set and they make an evenly spaced scale use it.
+ if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
+ // If very close to our whole number, use it.
+ if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
+ niceMin = generationOptions.min;
+ niceMax = generationOptions.max;
+ }
+ }
+
+ var numSpaces = (niceMax - niceMin) / spacing;
+ // If very close to our rounded value, use it.
+ if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
+ numSpaces = Math.round(numSpaces);
+ } else {
+ numSpaces = Math.ceil(numSpaces);
+ }
+
+ // Put the values into the ticks array
+ ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
+ for (var j = 1; j < numSpaces; ++j) {
+ ticks.push(niceMin + (j * spacing));
+ }
+ ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
+
+ return ticks;
+ },
+
+ /**
+ * Generate a set of logarithmic ticks
+ * @method Chart.Ticks.generators.logarithmic
+ * @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
+ * @param dataRange {IRange} the range of the data
+ * @returns {Array} array of tick values
+ */
+ logarithmic: function(generationOptions, dataRange) {
+ var ticks = [];
+ var valueOrDefault = helpers.valueOrDefault;
+
+ // Figure out what the max number of ticks we can support it is based on the size of
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
+ // the graph
+ var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
+
+ var endExp = Math.floor(helpers.log10(dataRange.max));
+ var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
+ var exp, significand;
+
+ if (tickVal === 0) {
+ exp = Math.floor(helpers.log10(dataRange.minNotZero));
+ significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
+
+ ticks.push(tickVal);
+ tickVal = significand * Math.pow(10, exp);
+ } else {
+ exp = Math.floor(helpers.log10(tickVal));
+ significand = Math.floor(tickVal / Math.pow(10, exp));
+ }
+
+ do {
+ ticks.push(tickVal);
+
+ ++significand;
+ if (significand === 10) {
+ significand = 1;
+ ++exp;
+ }
+
+ tickVal = significand * Math.pow(10, exp);
+ } while (exp < endExp || (exp === endExp && significand < endSignificand));
+
+ var lastTick = valueOrDefault(generationOptions.max, tickVal);
+ ticks.push(lastTick);
+
+ return ticks;
+ }
+ },
+
+ /**
+ * Namespace to hold formatters for different types of ticks
+ * @namespace Chart.Ticks.formatters
+ */
+ formatters: {
+ /**
+ * Formatter for value labels
+ * @method Chart.Ticks.formatters.values
+ * @param value the value to display
+ * @return {String|Array} the label to display
+ */
+ values: function(value) {
+ return helpers.isArray(value) ? value : '' + value;
+ },
+
+ /**
+ * Formatter for linear numeric ticks
+ * @method Chart.Ticks.formatters.linear
+ * @param tickValue {Number} the value to be formatted
+ * @param index {Number} the position of the tickValue parameter in the ticks array
+ * @param ticks {Array} the list of ticks being converted
+ * @return {String} string representation of the tickValue parameter
+ */
+ linear: function(tickValue, index, ticks) {
+ // If we have lots of ticks, don't use the ones
+ var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
+
+ // If we have a number like 2.5 as the delta, figure out how many decimal places we need
+ if (Math.abs(delta) > 1) {
+ if (tickValue !== Math.floor(tickValue)) {
+ // not an integer
+ delta = tickValue - Math.floor(tickValue);
+ }
+ }
+
+ var logDelta = helpers.log10(Math.abs(delta));
+ var tickString = '';
+
+ if (tickValue !== 0) {
+ var numDecimal = -1 * Math.floor(logDelta);
+ numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
+ tickString = tickValue.toFixed(numDecimal);
+ } else {
+ tickString = '0'; // never show decimal places for 0
+ }
+
+ return tickString;
+ },
+
+ logarithmic: function(tickValue, index, ticks) {
+ var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue))));
+
+ if (tickValue === 0) {
+ return '0';
+ } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
+ return tickValue.toExponential();
+ }
+ return '';
+ }
+ }
+};
+
+},{"45":45}],35:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+
+defaults._set('global', {
+ tooltips: {
+ enabled: true,
+ custom: null,
+ mode: 'nearest',
+ position: 'average',
+ intersect: true,
+ backgroundColor: 'rgba(0,0,0,0.8)',
+ titleFontStyle: 'bold',
+ titleSpacing: 2,
+ titleMarginBottom: 6,
+ titleFontColor: '#fff',
+ titleAlign: 'left',
+ bodySpacing: 2,
+ bodyFontColor: '#fff',
+ bodyAlign: 'left',
+ footerFontStyle: 'bold',
+ footerSpacing: 2,
+ footerMarginTop: 6,
+ footerFontColor: '#fff',
+ footerAlign: 'left',
+ yPadding: 6,
+ xPadding: 6,
+ caretPadding: 2,
+ caretSize: 5,
+ cornerRadius: 6,
+ multiKeyBackground: '#fff',
+ displayColors: true,
+ borderColor: 'rgba(0,0,0,0)',
+ borderWidth: 0,
+ callbacks: {
+ // Args are: (tooltipItems, data)
+ beforeTitle: helpers.noop,
+ title: function(tooltipItems, data) {
+ // Pick first xLabel for now
+ var title = '';
+ var labels = data.labels;
+ var labelCount = labels ? labels.length : 0;
+
+ if (tooltipItems.length > 0) {
+ var item = tooltipItems[0];
+
+ if (item.xLabel) {
+ title = item.xLabel;
+ } else if (labelCount > 0 && item.index < labelCount) {
+ title = labels[item.index];
+ }
+ }
+
+ return title;
+ },
+ afterTitle: helpers.noop,
+
+ // Args are: (tooltipItems, data)
+ beforeBody: helpers.noop,
+
+ // Args are: (tooltipItem, data)
+ beforeLabel: helpers.noop,
+ label: function(tooltipItem, data) {
+ var label = data.datasets[tooltipItem.datasetIndex].label || '';
+
+ if (label) {
+ label += ': ';
+ }
+ label += tooltipItem.yLabel;
+ return label;
+ },
+ labelColor: function(tooltipItem, chart) {
+ var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
+ var activeElement = meta.data[tooltipItem.index];
+ var view = activeElement._view;
+ return {
+ borderColor: view.borderColor,
+ backgroundColor: view.backgroundColor
+ };
+ },
+ labelTextColor: function() {
+ return this._options.bodyFontColor;
+ },
+ afterLabel: helpers.noop,
+
+ // Args are: (tooltipItems, data)
+ afterBody: helpers.noop,
+
+ // Args are: (tooltipItems, data)
+ beforeFooter: helpers.noop,
+ footer: helpers.noop,
+ afterFooter: helpers.noop
+ }
+ }
+});
+
+module.exports = function(Chart) {
+
+ /**
+ * Helper method to merge the opacity into a color
+ */
+ function mergeOpacity(colorString, opacity) {
+ var color = helpers.color(colorString);
+ return color.alpha(opacity * color.alpha()).rgbaString();
+ }
+
+ // Helper to push or concat based on if the 2nd parameter is an array or not
+ function pushOrConcat(base, toPush) {
+ if (toPush) {
+ if (helpers.isArray(toPush)) {
+ // base = base.concat(toPush);
+ Array.prototype.push.apply(base, toPush);
+ } else {
+ base.push(toPush);
+ }
+ }
+
+ return base;
+ }
+
+ // Private helper to create a tooltip item model
+ // @param element : the chart element (point, arc, bar) to create the tooltip item for
+ // @return : new tooltip item
+ function createTooltipItem(element) {
+ var xScale = element._xScale;
+ var yScale = element._yScale || element._scale; // handle radar || polarArea charts
+ var index = element._index;
+ var datasetIndex = element._datasetIndex;
+
+ return {
+ xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
+ yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
+ index: index,
+ datasetIndex: datasetIndex,
+ x: element._model.x,
+ y: element._model.y
+ };
+ }
+
+ /**
+ * Helper to get the reset model for the tooltip
+ * @param tooltipOpts {Object} the tooltip options
+ */
+ function getBaseModel(tooltipOpts) {
+ var globalDefaults = defaults.global;
+ var valueOrDefault = helpers.valueOrDefault;
+
+ return {
+ // Positioning
+ xPadding: tooltipOpts.xPadding,
+ yPadding: tooltipOpts.yPadding,
+ xAlign: tooltipOpts.xAlign,
+ yAlign: tooltipOpts.yAlign,
+
+ // Body
+ bodyFontColor: tooltipOpts.bodyFontColor,
+ _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
+ _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
+ _bodyAlign: tooltipOpts.bodyAlign,
+ bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
+ bodySpacing: tooltipOpts.bodySpacing,
+
+ // Title
+ titleFontColor: tooltipOpts.titleFontColor,
+ _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
+ _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
+ titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
+ _titleAlign: tooltipOpts.titleAlign,
+ titleSpacing: tooltipOpts.titleSpacing,
+ titleMarginBottom: tooltipOpts.titleMarginBottom,
+
+ // Footer
+ footerFontColor: tooltipOpts.footerFontColor,
+ _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
+ _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
+ footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
+ _footerAlign: tooltipOpts.footerAlign,
+ footerSpacing: tooltipOpts.footerSpacing,
+ footerMarginTop: tooltipOpts.footerMarginTop,
+
+ // Appearance
+ caretSize: tooltipOpts.caretSize,
+ cornerRadius: tooltipOpts.cornerRadius,
+ backgroundColor: tooltipOpts.backgroundColor,
+ opacity: 0,
+ legendColorBackground: tooltipOpts.multiKeyBackground,
+ displayColors: tooltipOpts.displayColors,
+ borderColor: tooltipOpts.borderColor,
+ borderWidth: tooltipOpts.borderWidth
+ };
+ }
+
+ /**
+ * Get the size of the tooltip
+ */
+ function getTooltipSize(tooltip, model) {
+ var ctx = tooltip._chart.ctx;
+
+ var height = model.yPadding * 2; // Tooltip Padding
+ var width = 0;
+
+ // Count of all lines in the body
+ var body = model.body;
+ var combinedBodyLength = body.reduce(function(count, bodyItem) {
+ return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
+ }, 0);
+ combinedBodyLength += model.beforeBody.length + model.afterBody.length;
+
+ var titleLineCount = model.title.length;
+ var footerLineCount = model.footer.length;
+ var titleFontSize = model.titleFontSize;
+ var bodyFontSize = model.bodyFontSize;
+ var footerFontSize = model.footerFontSize;
+
+ height += titleLineCount * titleFontSize; // Title Lines
+ height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
+ height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
+ height += combinedBodyLength * bodyFontSize; // Body Lines
+ height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
+ height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
+ height += footerLineCount * (footerFontSize); // Footer Lines
+ height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
+
+ // Title width
+ var widthPadding = 0;
+ var maxLineWidth = function(line) {
+ width = Math.max(width, ctx.measureText(line).width + widthPadding);
+ };
+
+ ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
+ helpers.each(model.title, maxLineWidth);
+
+ // Body width
+ ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
+ helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
+
+ // Body lines may include some extra width due to the color box
+ widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
+ helpers.each(body, function(bodyItem) {
+ helpers.each(bodyItem.before, maxLineWidth);
+ helpers.each(bodyItem.lines, maxLineWidth);
+ helpers.each(bodyItem.after, maxLineWidth);
+ });
+
+ // Reset back to 0
+ widthPadding = 0;
+
+ // Footer width
+ ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
+ helpers.each(model.footer, maxLineWidth);
+
+ // Add padding
+ width += 2 * model.xPadding;
+
+ return {
+ width: width,
+ height: height
+ };
+ }
+
+ /**
+ * Helper to get the alignment of a tooltip given the size
+ */
+ function determineAlignment(tooltip, size) {
+ var model = tooltip._model;
+ var chart = tooltip._chart;
+ var chartArea = tooltip._chart.chartArea;
+ var xAlign = 'center';
+ var yAlign = 'center';
+
+ if (model.y < size.height) {
+ yAlign = 'top';
+ } else if (model.y > (chart.height - size.height)) {
+ yAlign = 'bottom';
+ }
+
+ var lf, rf; // functions to determine left, right alignment
+ var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
+ var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
+ var midX = (chartArea.left + chartArea.right) / 2;
+ var midY = (chartArea.top + chartArea.bottom) / 2;
+
+ if (yAlign === 'center') {
+ lf = function(x) {
+ return x <= midX;
+ };
+ rf = function(x) {
+ return x > midX;
+ };
+ } else {
+ lf = function(x) {
+ return x <= (size.width / 2);
+ };
+ rf = function(x) {
+ return x >= (chart.width - (size.width / 2));
+ };
+ }
+
+ olf = function(x) {
+ return x + size.width > chart.width;
+ };
+ orf = function(x) {
+ return x - size.width < 0;
+ };
+ yf = function(y) {
+ return y <= midY ? 'top' : 'bottom';
+ };
+
+ if (lf(model.x)) {
+ xAlign = 'left';
+
+ // Is tooltip too wide and goes over the right side of the chart.?
+ if (olf(model.x)) {
+ xAlign = 'center';
+ yAlign = yf(model.y);
+ }
+ } else if (rf(model.x)) {
+ xAlign = 'right';
+
+ // Is tooltip too wide and goes outside left edge of canvas?
+ if (orf(model.x)) {
+ xAlign = 'center';
+ yAlign = yf(model.y);
+ }
+ }
+
+ var opts = tooltip._options;
+ return {
+ xAlign: opts.xAlign ? opts.xAlign : xAlign,
+ yAlign: opts.yAlign ? opts.yAlign : yAlign
+ };
+ }
+
+ /**
+ * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
+ */
+ function getBackgroundPoint(vm, size, alignment) {
+ // Background Position
+ var x = vm.x;
+ var y = vm.y;
+
+ var caretSize = vm.caretSize;
+ var caretPadding = vm.caretPadding;
+ var cornerRadius = vm.cornerRadius;
+ var xAlign = alignment.xAlign;
+ var yAlign = alignment.yAlign;
+ var paddingAndSize = caretSize + caretPadding;
+ var radiusAndPadding = cornerRadius + caretPadding;
+
+ if (xAlign === 'right') {
+ x -= size.width;
+ } else if (xAlign === 'center') {
+ x -= (size.width / 2);
+ }
+
+ if (yAlign === 'top') {
+ y += paddingAndSize;
+ } else if (yAlign === 'bottom') {
+ y -= size.height + paddingAndSize;
+ } else {
+ y -= (size.height / 2);
+ }
+
+ if (yAlign === 'center') {
+ if (xAlign === 'left') {
+ x += paddingAndSize;
+ } else if (xAlign === 'right') {
+ x -= paddingAndSize;
+ }
+ } else if (xAlign === 'left') {
+ x -= radiusAndPadding;
+ } else if (xAlign === 'right') {
+ x += radiusAndPadding;
+ }
+
+ return {
+ x: x,
+ y: y
+ };
+ }
+
+ Chart.Tooltip = Element.extend({
+ initialize: function() {
+ this._model = getBaseModel(this._options);
+ this._lastActive = [];
+ },
+
+ // Get the title
+ // Args are: (tooltipItem, data)
+ getTitle: function() {
+ var me = this;
+ var opts = me._options;
+ var callbacks = opts.callbacks;
+
+ var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
+ var title = callbacks.title.apply(me, arguments);
+ var afterTitle = callbacks.afterTitle.apply(me, arguments);
+
+ var lines = [];
+ lines = pushOrConcat(lines, beforeTitle);
+ lines = pushOrConcat(lines, title);
+ lines = pushOrConcat(lines, afterTitle);
+
+ return lines;
+ },
+
+ // Args are: (tooltipItem, data)
+ getBeforeBody: function() {
+ var lines = this._options.callbacks.beforeBody.apply(this, arguments);
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
+ },
+
+ // Args are: (tooltipItem, data)
+ getBody: function(tooltipItems, data) {
+ var me = this;
+ var callbacks = me._options.callbacks;
+ var bodyItems = [];
+
+ helpers.each(tooltipItems, function(tooltipItem) {
+ var bodyItem = {
+ before: [],
+ lines: [],
+ after: []
+ };
+ pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
+ pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
+ pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
+
+ bodyItems.push(bodyItem);
+ });
+
+ return bodyItems;
+ },
+
+ // Args are: (tooltipItem, data)
+ getAfterBody: function() {
+ var lines = this._options.callbacks.afterBody.apply(this, arguments);
+ return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
+ },
+
+ // Get the footer and beforeFooter and afterFooter lines
+ // Args are: (tooltipItem, data)
+ getFooter: function() {
+ var me = this;
+ var callbacks = me._options.callbacks;
+
+ var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
+ var footer = callbacks.footer.apply(me, arguments);
+ var afterFooter = callbacks.afterFooter.apply(me, arguments);
+
+ var lines = [];
+ lines = pushOrConcat(lines, beforeFooter);
+ lines = pushOrConcat(lines, footer);
+ lines = pushOrConcat(lines, afterFooter);
+
+ return lines;
+ },
+
+ update: function(changed) {
+ var me = this;
+ var opts = me._options;
+
+ // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
+ // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
+ // which breaks any animations.
+ var existingModel = me._model;
+ var model = me._model = getBaseModel(opts);
+ var active = me._active;
+
+ var data = me._data;
+
+ // In the case where active.length === 0 we need to keep these at existing values for good animations
+ var alignment = {
+ xAlign: existingModel.xAlign,
+ yAlign: existingModel.yAlign
+ };
+ var backgroundPoint = {
+ x: existingModel.x,
+ y: existingModel.y
+ };
+ var tooltipSize = {
+ width: existingModel.width,
+ height: existingModel.height
+ };
+ var tooltipPosition = {
+ x: existingModel.caretX,
+ y: existingModel.caretY
+ };
+
+ var i, len;
+
+ if (active.length) {
+ model.opacity = 1;
+
+ var labelColors = [];
+ var labelTextColors = [];
+ tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
+
+ var tooltipItems = [];
+ for (i = 0, len = active.length; i < len; ++i) {
+ tooltipItems.push(createTooltipItem(active[i]));
+ }
+
+ // If the user provided a filter function, use it to modify the tooltip items
+ if (opts.filter) {
+ tooltipItems = tooltipItems.filter(function(a) {
+ return opts.filter(a, data);
+ });
+ }
+
+ // If the user provided a sorting function, use it to modify the tooltip items
+ if (opts.itemSort) {
+ tooltipItems = tooltipItems.sort(function(a, b) {
+ return opts.itemSort(a, b, data);
+ });
+ }
+
+ // Determine colors for boxes
+ helpers.each(tooltipItems, function(tooltipItem) {
+ labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
+ labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
+ });
+
+
+ // Build the Text Lines
+ model.title = me.getTitle(tooltipItems, data);
+ model.beforeBody = me.getBeforeBody(tooltipItems, data);
+ model.body = me.getBody(tooltipItems, data);
+ model.afterBody = me.getAfterBody(tooltipItems, data);
+ model.footer = me.getFooter(tooltipItems, data);
+
+ // Initial positioning and colors
+ model.x = Math.round(tooltipPosition.x);
+ model.y = Math.round(tooltipPosition.y);
+ model.caretPadding = opts.caretPadding;
+ model.labelColors = labelColors;
+ model.labelTextColors = labelTextColors;
+
+ // data points
+ model.dataPoints = tooltipItems;
+
+ // We need to determine alignment of the tooltip
+ tooltipSize = getTooltipSize(this, model);
+ alignment = determineAlignment(this, tooltipSize);
+ // Final Size and Position
+ backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);
+ } else {
+ model.opacity = 0;
+ }
+
+ model.xAlign = alignment.xAlign;
+ model.yAlign = alignment.yAlign;
+ model.x = backgroundPoint.x;
+ model.y = backgroundPoint.y;
+ model.width = tooltipSize.width;
+ model.height = tooltipSize.height;
+
+ // Point where the caret on the tooltip points to
+ model.caretX = tooltipPosition.x;
+ model.caretY = tooltipPosition.y;
+
+ me._model = model;
+
+ if (changed && opts.custom) {
+ opts.custom.call(me, model);
+ }
+
+ return me;
+ },
+ drawCaret: function(tooltipPoint, size) {
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+ var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
+
+ ctx.lineTo(caretPosition.x1, caretPosition.y1);
+ ctx.lineTo(caretPosition.x2, caretPosition.y2);
+ ctx.lineTo(caretPosition.x3, caretPosition.y3);
+ },
+ getCaretPosition: function(tooltipPoint, size, vm) {
+ var x1, x2, x3, y1, y2, y3;
+ var caretSize = vm.caretSize;
+ var cornerRadius = vm.cornerRadius;
+ var xAlign = vm.xAlign;
+ var yAlign = vm.yAlign;
+ var ptX = tooltipPoint.x;
+ var ptY = tooltipPoint.y;
+ var width = size.width;
+ var height = size.height;
+
+ if (yAlign === 'center') {
+ y2 = ptY + (height / 2);
+
+ if (xAlign === 'left') {
+ x1 = ptX;
+ x2 = x1 - caretSize;
+ x3 = x1;
+
+ y1 = y2 + caretSize;
+ y3 = y2 - caretSize;
+ } else {
+ x1 = ptX + width;
+ x2 = x1 + caretSize;
+ x3 = x1;
+
+ y1 = y2 - caretSize;
+ y3 = y2 + caretSize;
+ }
+ } else {
+ if (xAlign === 'left') {
+ x2 = ptX + cornerRadius + (caretSize);
+ x1 = x2 - caretSize;
+ x3 = x2 + caretSize;
+ } else if (xAlign === 'right') {
+ x2 = ptX + width - cornerRadius - caretSize;
+ x1 = x2 - caretSize;
+ x3 = x2 + caretSize;
+ } else {
+ x2 = ptX + (width / 2);
+ x1 = x2 - caretSize;
+ x3 = x2 + caretSize;
+ }
+ if (yAlign === 'top') {
+ y1 = ptY;
+ y2 = y1 - caretSize;
+ y3 = y1;
+ } else {
+ y1 = ptY + height;
+ y2 = y1 + caretSize;
+ y3 = y1;
+ // invert drawing order
+ var tmp = x3;
+ x3 = x1;
+ x1 = tmp;
+ }
+ }
+ return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
+ },
+ drawTitle: function(pt, vm, ctx, opacity) {
+ var title = vm.title;
+
+ if (title.length) {
+ ctx.textAlign = vm._titleAlign;
+ ctx.textBaseline = 'top';
+
+ var titleFontSize = vm.titleFontSize;
+ var titleSpacing = vm.titleSpacing;
+
+ ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
+ ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
+
+ var i, len;
+ for (i = 0, len = title.length; i < len; ++i) {
+ ctx.fillText(title[i], pt.x, pt.y);
+ pt.y += titleFontSize + titleSpacing; // Line Height and spacing
+
+ if (i + 1 === title.length) {
+ pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
+ }
+ }
+ }
+ },
+ drawBody: function(pt, vm, ctx, opacity) {
+ var bodyFontSize = vm.bodyFontSize;
+ var bodySpacing = vm.bodySpacing;
+ var body = vm.body;
+
+ ctx.textAlign = vm._bodyAlign;
+ ctx.textBaseline = 'top';
+ ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
+
+ // Before Body
+ var xLinePadding = 0;
+ var fillLineOfText = function(line) {
+ ctx.fillText(line, pt.x + xLinePadding, pt.y);
+ pt.y += bodyFontSize + bodySpacing;
+ };
+
+ // Before body lines
+ ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
+ helpers.each(vm.beforeBody, fillLineOfText);
+
+ var drawColorBoxes = vm.displayColors;
+ xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
+
+ // Draw body lines now
+ helpers.each(body, function(bodyItem, i) {
+ var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
+ ctx.fillStyle = textColor;
+ helpers.each(bodyItem.before, fillLineOfText);
+
+ helpers.each(bodyItem.lines, function(line) {
+ // Draw Legend-like boxes if needed
+ if (drawColorBoxes) {
+ // Fill a white rect so that colours merge nicely if the opacity is < 1
+ ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
+ ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
+
+ // Border
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
+ ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
+
+ // Inner square
+ ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
+ ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
+ ctx.fillStyle = textColor;
+ }
+
+ fillLineOfText(line);
+ });
+
+ helpers.each(bodyItem.after, fillLineOfText);
+ });
+
+ // Reset back to 0 for after body
+ xLinePadding = 0;
+
+ // After body lines
+ helpers.each(vm.afterBody, fillLineOfText);
+ pt.y -= bodySpacing; // Remove last body spacing
+ },
+ drawFooter: function(pt, vm, ctx, opacity) {
+ var footer = vm.footer;
+
+ if (footer.length) {
+ pt.y += vm.footerMarginTop;
+
+ ctx.textAlign = vm._footerAlign;
+ ctx.textBaseline = 'top';
+
+ ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
+ ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
+
+ helpers.each(footer, function(line) {
+ ctx.fillText(line, pt.x, pt.y);
+ pt.y += vm.footerFontSize + vm.footerSpacing;
+ });
+ }
+ },
+ drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
+ ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
+ ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
+ ctx.lineWidth = vm.borderWidth;
+ var xAlign = vm.xAlign;
+ var yAlign = vm.yAlign;
+ var x = pt.x;
+ var y = pt.y;
+ var width = tooltipSize.width;
+ var height = tooltipSize.height;
+ var radius = vm.cornerRadius;
+
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ if (yAlign === 'top') {
+ this.drawCaret(pt, tooltipSize);
+ }
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ if (yAlign === 'center' && xAlign === 'right') {
+ this.drawCaret(pt, tooltipSize);
+ }
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ if (yAlign === 'bottom') {
+ this.drawCaret(pt, tooltipSize);
+ }
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ if (yAlign === 'center' && xAlign === 'left') {
+ this.drawCaret(pt, tooltipSize);
+ }
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+
+ ctx.fill();
+
+ if (vm.borderWidth > 0) {
+ ctx.stroke();
+ }
+ },
+ draw: function() {
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+
+ if (vm.opacity === 0) {
+ return;
+ }
+
+ var tooltipSize = {
+ width: vm.width,
+ height: vm.height
+ };
+ var pt = {
+ x: vm.x,
+ y: vm.y
+ };
+
+ // IE11/Edge does not like very small opacities, so snap to 0
+ var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
+
+ // Truthy/falsey value for empty tooltip
+ var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
+
+ if (this._options.enabled && hasTooltipContent) {
+ // Draw Background
+ this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
+
+ // Draw Title, Body, and Footer
+ pt.x += vm.xPadding;
+ pt.y += vm.yPadding;
+
+ // Titles
+ this.drawTitle(pt, vm, ctx, opacity);
+
+ // Body
+ this.drawBody(pt, vm, ctx, opacity);
+
+ // Footer
+ this.drawFooter(pt, vm, ctx, opacity);
+ }
+ },
+
+ /**
+ * Handle an event
+ * @private
+ * @param {IEvent} event - The event to handle
+ * @returns {Boolean} true if the tooltip changed
+ */
+ handleEvent: function(e) {
+ var me = this;
+ var options = me._options;
+ var changed = false;
+
+ me._lastActive = me._lastActive || [];
+
+ // Find Active Elements for tooltips
+ if (e.type === 'mouseout') {
+ me._active = [];
+ } else {
+ me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
+ }
+
+ // Remember Last Actives
+ changed = !helpers.arrayEquals(me._active, me._lastActive);
+
+ // If tooltip didn't change, do not handle the target event
+ if (!changed) {
+ return false;
+ }
+
+ me._lastActive = me._active;
+
+ if (options.enabled || options.custom) {
+ me._eventPosition = {
+ x: e.x,
+ y: e.y
+ };
+
+ var model = me._model;
+ me.update(true);
+ me.pivot();
+
+ // See if our tooltip position changed
+ changed |= (model.x !== me._model.x) || (model.y !== me._model.y);
+ }
+
+ return changed;
+ }
+ });
+
+ /**
+ * @namespace Chart.Tooltip.positioners
+ */
+ Chart.Tooltip.positioners = {
+ /**
+ * Average mode places the tooltip at the average position of the elements shown
+ * @function Chart.Tooltip.positioners.average
+ * @param elements {ChartElement[]} the elements being displayed in the tooltip
+ * @returns {Point} tooltip position
+ */
+ average: function(elements) {
+ if (!elements.length) {
+ return false;
+ }
+
+ var i, len;
+ var x = 0;
+ var y = 0;
+ var count = 0;
+
+ for (i = 0, len = elements.length; i < len; ++i) {
+ var el = elements[i];
+ if (el && el.hasValue()) {
+ var pos = el.tooltipPosition();
+ x += pos.x;
+ y += pos.y;
+ ++count;
+ }
+ }
+
+ return {
+ x: Math.round(x / count),
+ y: Math.round(y / count)
+ };
+ },
+
+ /**
+ * Gets the tooltip position nearest of the item nearest to the event position
+ * @function Chart.Tooltip.positioners.nearest
+ * @param elements {Chart.Element[]} the tooltip elements
+ * @param eventPosition {Point} the position of the event in canvas coordinates
+ * @returns {Point} the tooltip position
+ */
+ nearest: function(elements, eventPosition) {
+ var x = eventPosition.x;
+ var y = eventPosition.y;
+ var minDistance = Number.POSITIVE_INFINITY;
+ var i, len, nearestElement;
+
+ for (i = 0, len = elements.length; i < len; ++i) {
+ var el = elements[i];
+ if (el && el.hasValue()) {
+ var center = el.getCenterPoint();
+ var d = helpers.distanceBetweenPoints(eventPosition, center);
+
+ if (d < minDistance) {
+ minDistance = d;
+ nearestElement = el;
+ }
+ }
+ }
+
+ if (nearestElement) {
+ var tp = nearestElement.tooltipPosition();
+ x = tp.x;
+ y = tp.y;
+ }
+
+ return {
+ x: x,
+ y: y
+ };
+ }
+ };
+};
+
+},{"25":25,"26":26,"45":45}],36:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+
+defaults._set('global', {
+ elements: {
+ arc: {
+ backgroundColor: defaults.global.defaultColor,
+ borderColor: '#fff',
+ borderWidth: 2
+ }
+ }
+});
+
+module.exports = Element.extend({
+ inLabelRange: function(mouseX) {
+ var vm = this._view;
+
+ if (vm) {
+ return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
+ }
+ return false;
+ },
+
+ inRange: function(chartX, chartY) {
+ var vm = this._view;
+
+ if (vm) {
+ var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
+ var angle = pointRelativePosition.angle;
+ var distance = pointRelativePosition.distance;
+
+ // Sanitise angle range
+ var startAngle = vm.startAngle;
+ var endAngle = vm.endAngle;
+ while (endAngle < startAngle) {
+ endAngle += 2.0 * Math.PI;
+ }
+ while (angle > endAngle) {
+ angle -= 2.0 * Math.PI;
+ }
+ while (angle < startAngle) {
+ angle += 2.0 * Math.PI;
+ }
+
+ // Check if within the range of the open/close angle
+ var betweenAngles = (angle >= startAngle && angle <= endAngle);
+ var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
+
+ return (betweenAngles && withinRadius);
+ }
+ return false;
+ },
+
+ getCenterPoint: function() {
+ var vm = this._view;
+ var halfAngle = (vm.startAngle + vm.endAngle) / 2;
+ var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
+ return {
+ x: vm.x + Math.cos(halfAngle) * halfRadius,
+ y: vm.y + Math.sin(halfAngle) * halfRadius
+ };
+ },
+
+ getArea: function() {
+ var vm = this._view;
+ return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
+ },
+
+ tooltipPosition: function() {
+ var vm = this._view;
+ var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
+ var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
+
+ return {
+ x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
+ y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
+ };
+ },
+
+ draw: function() {
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+ var sA = vm.startAngle;
+ var eA = vm.endAngle;
+
+ ctx.beginPath();
+
+ ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
+ ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
+
+ ctx.closePath();
+ ctx.strokeStyle = vm.borderColor;
+ ctx.lineWidth = vm.borderWidth;
+
+ ctx.fillStyle = vm.backgroundColor;
+
+ ctx.fill();
+ ctx.lineJoin = 'bevel';
+
+ if (vm.borderWidth) {
+ ctx.stroke();
+ }
+ }
+});
+
+},{"25":25,"26":26,"45":45}],37:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+
+var globalDefaults = defaults.global;
+
+defaults._set('global', {
+ elements: {
+ line: {
+ tension: 0.4,
+ backgroundColor: globalDefaults.defaultColor,
+ borderWidth: 3,
+ borderColor: globalDefaults.defaultColor,
+ borderCapStyle: 'butt',
+ borderDash: [],
+ borderDashOffset: 0.0,
+ borderJoinStyle: 'miter',
+ capBezierPoints: true,
+ fill: true, // do we fill in the area between the line and its base axis
+ }
+ }
+});
+
+module.exports = Element.extend({
+ draw: function() {
+ var me = this;
+ var vm = me._view;
+ var ctx = me._chart.ctx;
+ var spanGaps = vm.spanGaps;
+ var points = me._children.slice(); // clone array
+ var globalOptionLineElements = globalDefaults.elements.line;
+ var lastDrawnIndex = -1;
+ var index, current, previous, currentVM;
+
+ // If we are looping, adding the first point again
+ if (me._loop && points.length) {
+ points.push(points[0]);
+ }
+
+ ctx.save();
+
+ // Stroke Line Options
+ ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
+
+ // IE 9 and 10 do not support line dash
+ if (ctx.setLineDash) {
+ ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
+ }
+
+ ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset;
+ ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
+ ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth;
+ ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
+
+ // Stroke Line
+ ctx.beginPath();
+ lastDrawnIndex = -1;
+
+ for (index = 0; index < points.length; ++index) {
+ current = points[index];
+ previous = helpers.previousItem(points, index);
+ currentVM = current._view;
+
+ // First point moves to it's starting position no matter what
+ if (index === 0) {
+ if (!currentVM.skip) {
+ ctx.moveTo(currentVM.x, currentVM.y);
+ lastDrawnIndex = index;
+ }
+ } else {
+ previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex];
+
+ if (!currentVM.skip) {
+ if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
+ // There was a gap and this is the first point after the gap
+ ctx.moveTo(currentVM.x, currentVM.y);
+ } else {
+ // Line to next point
+ helpers.canvas.lineTo(ctx, previous._view, current._view);
+ }
+ lastDrawnIndex = index;
+ }
+ }
+ }
+
+ ctx.stroke();
+ ctx.restore();
+ }
+});
+
+},{"25":25,"26":26,"45":45}],38:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+
+var defaultColor = defaults.global.defaultColor;
+
+defaults._set('global', {
+ elements: {
+ point: {
+ radius: 3,
+ pointStyle: 'circle',
+ backgroundColor: defaultColor,
+ borderColor: defaultColor,
+ borderWidth: 1,
+ // Hover
+ hitRadius: 1,
+ hoverRadius: 4,
+ hoverBorderWidth: 1
+ }
+ }
+});
+
+function xRange(mouseX) {
+ var vm = this._view;
+ return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
+}
+
+function yRange(mouseY) {
+ var vm = this._view;
+ return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
+}
+
+module.exports = Element.extend({
+ inRange: function(mouseX, mouseY) {
+ var vm = this._view;
+ return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
+ },
+
+ inLabelRange: xRange,
+ inXRange: xRange,
+ inYRange: yRange,
+
+ getCenterPoint: function() {
+ var vm = this._view;
+ return {
+ x: vm.x,
+ y: vm.y
+ };
+ },
+
+ getArea: function() {
+ return Math.PI * Math.pow(this._view.radius, 2);
+ },
+
+ tooltipPosition: function() {
+ var vm = this._view;
+ return {
+ x: vm.x,
+ y: vm.y,
+ padding: vm.radius + vm.borderWidth
+ };
+ },
+
+ draw: function(chartArea) {
+ var vm = this._view;
+ var model = this._model;
+ var ctx = this._chart.ctx;
+ var pointStyle = vm.pointStyle;
+ var radius = vm.radius;
+ var x = vm.x;
+ var y = vm.y;
+ var color = helpers.color;
+ var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.)
+ var ratio = 0;
+
+ if (vm.skip) {
+ return;
+ }
+
+ ctx.strokeStyle = vm.borderColor || defaultColor;
+ ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth);
+ ctx.fillStyle = vm.backgroundColor || defaultColor;
+
+ // Cliping for Points.
+ // going out from inner charArea?
+ if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right * errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom * errMargin < model.y))) {
+ // Point fade out
+ if (model.x < chartArea.left) {
+ ratio = (x - model.x) / (chartArea.left - model.x);
+ } else if (chartArea.right * errMargin < model.x) {
+ ratio = (model.x - x) / (model.x - chartArea.right);
+ } else if (model.y < chartArea.top) {
+ ratio = (y - model.y) / (chartArea.top - model.y);
+ } else if (chartArea.bottom * errMargin < model.y) {
+ ratio = (model.y - y) / (model.y - chartArea.bottom);
+ }
+ ratio = Math.round(ratio * 100) / 100;
+ ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString();
+ ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
+ }
+
+ helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
+ }
+});
+
+},{"25":25,"26":26,"45":45}],39:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+
+defaults._set('global', {
+ elements: {
+ rectangle: {
+ backgroundColor: defaults.global.defaultColor,
+ borderColor: defaults.global.defaultColor,
+ borderSkipped: 'bottom',
+ borderWidth: 0
+ }
+ }
+});
+
+function isVertical(bar) {
+ return bar._view.width !== undefined;
+}
+
+/**
+ * Helper function to get the bounds of the bar regardless of the orientation
+ * @param bar {Chart.Element.Rectangle} the bar
+ * @return {Bounds} bounds of the bar
+ * @private
+ */
+function getBarBounds(bar) {
+ var vm = bar._view;
+ var x1, x2, y1, y2;
+
+ if (isVertical(bar)) {
+ // vertical
+ var halfWidth = vm.width / 2;
+ x1 = vm.x - halfWidth;
+ x2 = vm.x + halfWidth;
+ y1 = Math.min(vm.y, vm.base);
+ y2 = Math.max(vm.y, vm.base);
+ } else {
+ // horizontal bar
+ var halfHeight = vm.height / 2;
+ x1 = Math.min(vm.x, vm.base);
+ x2 = Math.max(vm.x, vm.base);
+ y1 = vm.y - halfHeight;
+ y2 = vm.y + halfHeight;
+ }
+
+ return {
+ left: x1,
+ top: y1,
+ right: x2,
+ bottom: y2
+ };
+}
+
+module.exports = Element.extend({
+ draw: function() {
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+ var left, right, top, bottom, signX, signY, borderSkipped;
+ var borderWidth = vm.borderWidth;
+
+ if (!vm.horizontal) {
+ // bar
+ left = vm.x - vm.width / 2;
+ right = vm.x + vm.width / 2;
+ top = vm.y;
+ bottom = vm.base;
+ signX = 1;
+ signY = bottom > top ? 1 : -1;
+ borderSkipped = vm.borderSkipped || 'bottom';
+ } else {
+ // horizontal bar
+ left = vm.base;
+ right = vm.x;
+ top = vm.y - vm.height / 2;
+ bottom = vm.y + vm.height / 2;
+ signX = right > left ? 1 : -1;
+ signY = 1;
+ borderSkipped = vm.borderSkipped || 'left';
+ }
+
+ // Canvas doesn't allow us to stroke inside the width so we can
+ // adjust the sizes to fit if we're setting a stroke on the line
+ if (borderWidth) {
+ // borderWidth shold be less than bar width and bar height.
+ var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom));
+ borderWidth = borderWidth > barSize ? barSize : borderWidth;
+ var halfStroke = borderWidth / 2;
+ // Adjust borderWidth when bar top position is near vm.base(zero).
+ var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0);
+ var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0);
+ var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0);
+ var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0);
+ // not become a vertical line?
+ if (borderLeft !== borderRight) {
+ top = borderTop;
+ bottom = borderBottom;
+ }
+ // not become a horizontal line?
+ if (borderTop !== borderBottom) {
+ left = borderLeft;
+ right = borderRight;
+ }
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = vm.backgroundColor;
+ ctx.strokeStyle = vm.borderColor;
+ ctx.lineWidth = borderWidth;
+
+ // Corner points, from bottom-left to bottom-right clockwise
+ // | 1 2 |
+ // | 0 3 |
+ var corners = [
+ [left, bottom],
+ [left, top],
+ [right, top],
+ [right, bottom]
+ ];
+
+ // Find first (starting) corner with fallback to 'bottom'
+ var borders = ['bottom', 'left', 'top', 'right'];
+ var startCorner = borders.indexOf(borderSkipped, 0);
+ if (startCorner === -1) {
+ startCorner = 0;
+ }
+
+ function cornerAt(index) {
+ return corners[(startCorner + index) % 4];
+ }
+
+ // Draw rectangle from 'startCorner'
+ var corner = cornerAt(0);
+ ctx.moveTo(corner[0], corner[1]);
+
+ for (var i = 1; i < 4; i++) {
+ corner = cornerAt(i);
+ ctx.lineTo(corner[0], corner[1]);
+ }
+
+ ctx.fill();
+ if (borderWidth) {
+ ctx.stroke();
+ }
+ },
+
+ height: function() {
+ var vm = this._view;
+ return vm.base - vm.y;
+ },
+
+ inRange: function(mouseX, mouseY) {
+ var inRange = false;
+
+ if (this._view) {
+ var bounds = getBarBounds(this);
+ inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
+ }
+
+ return inRange;
+ },
+
+ inLabelRange: function(mouseX, mouseY) {
+ var me = this;
+ if (!me._view) {
+ return false;
+ }
+
+ var inRange = false;
+ var bounds = getBarBounds(me);
+
+ if (isVertical(me)) {
+ inRange = mouseX >= bounds.left && mouseX <= bounds.right;
+ } else {
+ inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
+ }
+
+ return inRange;
+ },
+
+ inXRange: function(mouseX) {
+ var bounds = getBarBounds(this);
+ return mouseX >= bounds.left && mouseX <= bounds.right;
+ },
+
+ inYRange: function(mouseY) {
+ var bounds = getBarBounds(this);
+ return mouseY >= bounds.top && mouseY <= bounds.bottom;
+ },
+
+ getCenterPoint: function() {
+ var vm = this._view;
+ var x, y;
+ if (isVertical(this)) {
+ x = vm.x;
+ y = (vm.y + vm.base) / 2;
+ } else {
+ x = (vm.x + vm.base) / 2;
+ y = vm.y;
+ }
+
+ return {x: x, y: y};
+ },
+
+ getArea: function() {
+ var vm = this._view;
+ return vm.width * Math.abs(vm.y - vm.base);
+ },
+
+ tooltipPosition: function() {
+ var vm = this._view;
+ return {
+ x: vm.x,
+ y: vm.y
+ };
+ }
+});
+
+},{"25":25,"26":26}],40:[function(require,module,exports){
+'use strict';
+
+module.exports = {};
+module.exports.Arc = require(36);
+module.exports.Line = require(37);
+module.exports.Point = require(38);
+module.exports.Rectangle = require(39);
+
+},{"36":36,"37":37,"38":38,"39":39}],41:[function(require,module,exports){
+'use strict';
+
+var helpers = require(42);
+
+/**
+ * @namespace Chart.helpers.canvas
+ */
+var exports = module.exports = {
+ /**
+ * Clears the entire canvas associated to the given `chart`.
+ * @param {Chart} chart - The chart for which to clear the canvas.
+ */
+ clear: function(chart) {
+ chart.ctx.clearRect(0, 0, chart.width, chart.height);
+ },
+
+ /**
+ * Creates a "path" for a rectangle with rounded corners at position (x, y) with a
+ * given size (width, height) and the same `radius` for all corners.
+ * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
+ * @param {Number} x - The x axis of the coordinate for the rectangle starting point.
+ * @param {Number} y - The y axis of the coordinate for the rectangle starting point.
+ * @param {Number} width - The rectangle's width.
+ * @param {Number} height - The rectangle's height.
+ * @param {Number} radius - The rounded amount (in pixels) for the four corners.
+ * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
+ */
+ roundedRect: function(ctx, x, y, width, height, radius) {
+ if (radius) {
+ var rx = Math.min(radius, width / 2);
+ var ry = Math.min(radius, height / 2);
+
+ ctx.moveTo(x + rx, y);
+ ctx.lineTo(x + width - rx, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + ry);
+ ctx.lineTo(x + width, y + height - ry);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
+ ctx.lineTo(x + rx, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - ry);
+ ctx.lineTo(x, y + ry);
+ ctx.quadraticCurveTo(x, y, x + rx, y);
+ } else {
+ ctx.rect(x, y, width, height);
+ }
+ },
+
+ drawPoint: function(ctx, style, radius, x, y) {
+ var type, edgeLength, xOffset, yOffset, height, size;
+
+ if (style && typeof style === 'object') {
+ type = style.toString();
+ if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
+ ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
+ return;
+ }
+ }
+
+ if (isNaN(radius) || radius <= 0) {
+ return;
+ }
+
+ switch (style) {
+ // Default includes circle
+ default:
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, Math.PI * 2);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case 'triangle':
+ ctx.beginPath();
+ edgeLength = 3 * radius / Math.sqrt(3);
+ height = edgeLength * Math.sqrt(3) / 2;
+ ctx.moveTo(x - edgeLength / 2, y + height / 3);
+ ctx.lineTo(x + edgeLength / 2, y + height / 3);
+ ctx.lineTo(x, y - 2 * height / 3);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case 'rect':
+ size = 1 / Math.SQRT2 * radius;
+ ctx.beginPath();
+ ctx.fillRect(x - size, y - size, 2 * size, 2 * size);
+ ctx.strokeRect(x - size, y - size, 2 * size, 2 * size);
+ break;
+ case 'rectRounded':
+ var offset = radius / Math.SQRT2;
+ var leftX = x - offset;
+ var topY = y - offset;
+ var sideSize = Math.SQRT2 * radius;
+ ctx.beginPath();
+ this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case 'rectRot':
+ size = 1 / Math.SQRT2 * radius;
+ ctx.beginPath();
+ ctx.moveTo(x - size, y);
+ ctx.lineTo(x, y + size);
+ ctx.lineTo(x + size, y);
+ ctx.lineTo(x, y - size);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case 'cross':
+ ctx.beginPath();
+ ctx.moveTo(x, y + radius);
+ ctx.lineTo(x, y - radius);
+ ctx.moveTo(x - radius, y);
+ ctx.lineTo(x + radius, y);
+ ctx.closePath();
+ break;
+ case 'crossRot':
+ ctx.beginPath();
+ xOffset = Math.cos(Math.PI / 4) * radius;
+ yOffset = Math.sin(Math.PI / 4) * radius;
+ ctx.moveTo(x - xOffset, y - yOffset);
+ ctx.lineTo(x + xOffset, y + yOffset);
+ ctx.moveTo(x - xOffset, y + yOffset);
+ ctx.lineTo(x + xOffset, y - yOffset);
+ ctx.closePath();
+ break;
+ case 'star':
+ ctx.beginPath();
+ ctx.moveTo(x, y + radius);
+ ctx.lineTo(x, y - radius);
+ ctx.moveTo(x - radius, y);
+ ctx.lineTo(x + radius, y);
+ xOffset = Math.cos(Math.PI / 4) * radius;
+ yOffset = Math.sin(Math.PI / 4) * radius;
+ ctx.moveTo(x - xOffset, y - yOffset);
+ ctx.lineTo(x + xOffset, y + yOffset);
+ ctx.moveTo(x - xOffset, y + yOffset);
+ ctx.lineTo(x + xOffset, y - yOffset);
+ ctx.closePath();
+ break;
+ case 'line':
+ ctx.beginPath();
+ ctx.moveTo(x - radius, y);
+ ctx.lineTo(x + radius, y);
+ ctx.closePath();
+ break;
+ case 'dash':
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + radius, y);
+ ctx.closePath();
+ break;
+ }
+
+ ctx.stroke();
+ },
+
+ clipArea: function(ctx, area) {
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
+ ctx.clip();
+ },
+
+ unclipArea: function(ctx) {
+ ctx.restore();
+ },
+
+ lineTo: function(ctx, previous, target, flip) {
+ if (target.steppedLine) {
+ if ((target.steppedLine === 'after' && !flip) || (target.steppedLine !== 'after' && flip)) {
+ ctx.lineTo(previous.x, target.y);
+ } else {
+ ctx.lineTo(target.x, previous.y);
+ }
+ ctx.lineTo(target.x, target.y);
+ return;
+ }
+
+ if (!target.tension) {
+ ctx.lineTo(target.x, target.y);
+ return;
+ }
+
+ ctx.bezierCurveTo(
+ flip ? previous.controlPointPreviousX : previous.controlPointNextX,
+ flip ? previous.controlPointPreviousY : previous.controlPointNextY,
+ flip ? target.controlPointNextX : target.controlPointPreviousX,
+ flip ? target.controlPointNextY : target.controlPointPreviousY,
+ target.x,
+ target.y);
+ }
+};
+
+// DEPRECATIONS
+
+/**
+ * Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
+ * @namespace Chart.helpers.clear
+ * @deprecated since version 2.7.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.clear = exports.clear;
+
+/**
+ * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
+ * @namespace Chart.helpers.drawRoundedRectangle
+ * @deprecated since version 2.7.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.drawRoundedRectangle = function(ctx) {
+ ctx.beginPath();
+ exports.roundedRect.apply(exports, arguments);
+ ctx.closePath();
+};
+
+},{"42":42}],42:[function(require,module,exports){
+'use strict';
+
+/**
+ * @namespace Chart.helpers
+ */
+var helpers = {
+ /**
+ * An empty function that can be used, for example, for optional callback.
+ */
+ noop: function() {},
+
+ /**
+ * Returns a unique id, sequentially generated from a global variable.
+ * @returns {Number}
+ * @function
+ */
+ uid: (function() {
+ var id = 0;
+ return function() {
+ return id++;
+ };
+ }()),
+
+ /**
+ * Returns true if `value` is neither null nor undefined, else returns false.
+ * @param {*} value - The value to test.
+ * @returns {Boolean}
+ * @since 2.7.0
+ */
+ isNullOrUndef: function(value) {
+ return value === null || typeof value === 'undefined';
+ },
+
+ /**
+ * Returns true if `value` is an array, else returns false.
+ * @param {*} value - The value to test.
+ * @returns {Boolean}
+ * @function
+ */
+ isArray: Array.isArray ? Array.isArray : function(value) {
+ return Object.prototype.toString.call(value) === '[object Array]';
+ },
+
+ /**
+ * Returns true if `value` is an object (excluding null), else returns false.
+ * @param {*} value - The value to test.
+ * @returns {Boolean}
+ * @since 2.7.0
+ */
+ isObject: function(value) {
+ return value !== null && Object.prototype.toString.call(value) === '[object Object]';
+ },
+
+ /**
+ * Returns `value` if defined, else returns `defaultValue`.
+ * @param {*} value - The value to return if defined.
+ * @param {*} defaultValue - The value to return if `value` is undefined.
+ * @returns {*}
+ */
+ valueOrDefault: function(value, defaultValue) {
+ return typeof value === 'undefined' ? defaultValue : value;
+ },
+
+ /**
+ * Returns value at the given `index` in array if defined, else returns `defaultValue`.
+ * @param {Array} value - The array to lookup for value at `index`.
+ * @param {Number} index - The index in `value` to lookup for value.
+ * @param {*} defaultValue - The value to return if `value[index]` is undefined.
+ * @returns {*}
+ */
+ valueAtIndexOrDefault: function(value, index, defaultValue) {
+ return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
+ },
+
+ /**
+ * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
+ * value returned by `fn`. If `fn` is not a function, this method returns undefined.
+ * @param {Function} fn - The function to call.
+ * @param {Array|undefined|null} args - The arguments with which `fn` should be called.
+ * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
+ * @returns {*}
+ */
+ callback: function(fn, args, thisArg) {
+ if (fn && typeof fn.call === 'function') {
+ return fn.apply(thisArg, args);
+ }
+ },
+
+ /**
+ * Note(SB) for performance sake, this method should only be used when loopable type
+ * is unknown or in none intensive code (not called often and small loopable). Else
+ * it's preferable to use a regular for() loop and save extra function calls.
+ * @param {Object|Array} loopable - The object or array to be iterated.
+ * @param {Function} fn - The function to call for each item.
+ * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
+ * @param {Boolean} [reverse] - If true, iterates backward on the loopable.
+ */
+ each: function(loopable, fn, thisArg, reverse) {
+ var i, len, keys;
+ if (helpers.isArray(loopable)) {
+ len = loopable.length;
+ if (reverse) {
+ for (i = len - 1; i >= 0; i--) {
+ fn.call(thisArg, loopable[i], i);
+ }
+ } else {
+ for (i = 0; i < len; i++) {
+ fn.call(thisArg, loopable[i], i);
+ }
+ }
+ } else if (helpers.isObject(loopable)) {
+ keys = Object.keys(loopable);
+ len = keys.length;
+ for (i = 0; i < len; i++) {
+ fn.call(thisArg, loopable[keys[i]], keys[i]);
+ }
+ }
+ },
+
+ /**
+ * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
+ * @see http://stackoverflow.com/a/14853974
+ * @param {Array} a0 - The array to compare
+ * @param {Array} a1 - The array to compare
+ * @returns {Boolean}
+ */
+ arrayEquals: function(a0, a1) {
+ var i, ilen, v0, v1;
+
+ if (!a0 || !a1 || a0.length !== a1.length) {
+ return false;
+ }
+
+ for (i = 0, ilen = a0.length; i < ilen; ++i) {
+ v0 = a0[i];
+ v1 = a1[i];
+
+ if (v0 instanceof Array && v1 instanceof Array) {
+ if (!helpers.arrayEquals(v0, v1)) {
+ return false;
+ }
+ } else if (v0 !== v1) {
+ // NOTE: two different object instances will never be equal: {x:20} != {x:20}
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Returns a deep copy of `source` without keeping references on objects and arrays.
+ * @param {*} source - The value to clone.
+ * @returns {*}
+ */
+ clone: function(source) {
+ if (helpers.isArray(source)) {
+ return source.map(helpers.clone);
+ }
+
+ if (helpers.isObject(source)) {
+ var target = {};
+ var keys = Object.keys(source);
+ var klen = keys.length;
+ var k = 0;
+
+ for (; k < klen; ++k) {
+ target[keys[k]] = helpers.clone(source[keys[k]]);
+ }
+
+ return target;
+ }
+
+ return source;
+ },
+
+ /**
+ * The default merger when Chart.helpers.merge is called without merger option.
+ * Note(SB): this method is also used by configMerge and scaleMerge as fallback.
+ * @private
+ */
+ _merger: function(key, target, source, options) {
+ var tval = target[key];
+ var sval = source[key];
+
+ if (helpers.isObject(tval) && helpers.isObject(sval)) {
+ helpers.merge(tval, sval, options);
+ } else {
+ target[key] = helpers.clone(sval);
+ }
+ },
+
+ /**
+ * Merges source[key] in target[key] only if target[key] is undefined.
+ * @private
+ */
+ _mergerIf: function(key, target, source) {
+ var tval = target[key];
+ var sval = source[key];
+
+ if (helpers.isObject(tval) && helpers.isObject(sval)) {
+ helpers.mergeIf(tval, sval);
+ } else if (!target.hasOwnProperty(key)) {
+ target[key] = helpers.clone(sval);
+ }
+ },
+
+ /**
+ * Recursively deep copies `source` properties into `target` with the given `options`.
+ * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
+ * @param {Object} target - The target object in which all sources are merged into.
+ * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
+ * @param {Object} [options] - Merging options:
+ * @param {Function} [options.merger] - The merge method (key, target, source, options)
+ * @returns {Object} The `target` object.
+ */
+ merge: function(target, source, options) {
+ var sources = helpers.isArray(source) ? source : [source];
+ var ilen = sources.length;
+ var merge, i, keys, klen, k;
+
+ if (!helpers.isObject(target)) {
+ return target;
+ }
+
+ options = options || {};
+ merge = options.merger || helpers._merger;
+
+ for (i = 0; i < ilen; ++i) {
+ source = sources[i];
+ if (!helpers.isObject(source)) {
+ continue;
+ }
+
+ keys = Object.keys(source);
+ for (k = 0, klen = keys.length; k < klen; ++k) {
+ merge(keys[k], target, source, options);
+ }
+ }
+
+ return target;
+ },
+
+ /**
+ * Recursively deep copies `source` properties into `target` *only* if not defined in target.
+ * IMPORTANT: `target` is not cloned and will be updated with `source` properties.
+ * @param {Object} target - The target object in which all sources are merged into.
+ * @param {Object|Array(Object)} source - Object(s) to merge into `target`.
+ * @returns {Object} The `target` object.
+ */
+ mergeIf: function(target, source) {
+ return helpers.merge(target, source, {merger: helpers._mergerIf});
+ },
+
+ /**
+ * Applies the contents of two or more objects together into the first object.
+ * @param {Object} target - The target object in which all objects are merged into.
+ * @param {Object} arg1 - Object containing additional properties to merge in target.
+ * @param {Object} argN - Additional objects containing properties to merge in target.
+ * @returns {Object} The `target` object.
+ */
+ extend: function(target) {
+ var setFn = function(value, key) {
+ target[key] = value;
+ };
+ for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
+ helpers.each(arguments[i], setFn);
+ }
+ return target;
+ },
+
+ /**
+ * Basic javascript inheritance based on the model created in Backbone.js
+ */
+ inherits: function(extensions) {
+ var me = this;
+ var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
+ return me.apply(this, arguments);
+ };
+
+ var Surrogate = function() {
+ this.constructor = ChartElement;
+ };
+
+ Surrogate.prototype = me.prototype;
+ ChartElement.prototype = new Surrogate();
+ ChartElement.extend = helpers.inherits;
+
+ if (extensions) {
+ helpers.extend(ChartElement.prototype, extensions);
+ }
+
+ ChartElement.__super__ = me.prototype;
+ return ChartElement;
+ }
+};
+
+module.exports = helpers;
+
+// DEPRECATIONS
+
+/**
+ * Provided for backward compatibility, use Chart.helpers.callback instead.
+ * @function Chart.helpers.callCallback
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.callCallback = helpers.callback;
+
+/**
+ * Provided for backward compatibility, use Array.prototype.indexOf instead.
+ * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
+ * @function Chart.helpers.indexOf
+ * @deprecated since version 2.7.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.indexOf = function(array, item, fromIndex) {
+ return Array.prototype.indexOf.call(array, item, fromIndex);
+};
+
+/**
+ * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
+ * @function Chart.helpers.getValueOrDefault
+ * @deprecated since version 2.7.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.getValueOrDefault = helpers.valueOrDefault;
+
+/**
+ * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
+ * @function Chart.helpers.getValueAtIndexOrDefault
+ * @deprecated since version 2.7.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
+
+},{}],43:[function(require,module,exports){
+'use strict';
+
+var helpers = require(42);
+
+/**
+ * Easing functions adapted from Robert Penner's easing equations.
+ * @namespace Chart.helpers.easingEffects
+ * @see http://www.robertpenner.com/easing/
+ */
+var effects = {
+ linear: function(t) {
+ return t;
+ },
+
+ easeInQuad: function(t) {
+ return t * t;
+ },
+
+ easeOutQuad: function(t) {
+ return -t * (t - 2);
+ },
+
+ easeInOutQuad: function(t) {
+ if ((t /= 0.5) < 1) {
+ return 0.5 * t * t;
+ }
+ return -0.5 * ((--t) * (t - 2) - 1);
+ },
+
+ easeInCubic: function(t) {
+ return t * t * t;
+ },
+
+ easeOutCubic: function(t) {
+ return (t = t - 1) * t * t + 1;
+ },
+
+ easeInOutCubic: function(t) {
+ if ((t /= 0.5) < 1) {
+ return 0.5 * t * t * t;
+ }
+ return 0.5 * ((t -= 2) * t * t + 2);
+ },
+
+ easeInQuart: function(t) {
+ return t * t * t * t;
+ },
+
+ easeOutQuart: function(t) {
+ return -((t = t - 1) * t * t * t - 1);
+ },
+
+ easeInOutQuart: function(t) {
+ if ((t /= 0.5) < 1) {
+ return 0.5 * t * t * t * t;
+ }
+ return -0.5 * ((t -= 2) * t * t * t - 2);
+ },
+
+ easeInQuint: function(t) {
+ return t * t * t * t * t;
+ },
+
+ easeOutQuint: function(t) {
+ return (t = t - 1) * t * t * t * t + 1;
+ },
+
+ easeInOutQuint: function(t) {
+ if ((t /= 0.5) < 1) {
+ return 0.5 * t * t * t * t * t;
+ }
+ return 0.5 * ((t -= 2) * t * t * t * t + 2);
+ },
+
+ easeInSine: function(t) {
+ return -Math.cos(t * (Math.PI / 2)) + 1;
+ },
+
+ easeOutSine: function(t) {
+ return Math.sin(t * (Math.PI / 2));
+ },
+
+ easeInOutSine: function(t) {
+ return -0.5 * (Math.cos(Math.PI * t) - 1);
+ },
+
+ easeInExpo: function(t) {
+ return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
+ },
+
+ easeOutExpo: function(t) {
+ return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
+ },
+
+ easeInOutExpo: function(t) {
+ if (t === 0) {
+ return 0;
+ }
+ if (t === 1) {
+ return 1;
+ }
+ if ((t /= 0.5) < 1) {
+ return 0.5 * Math.pow(2, 10 * (t - 1));
+ }
+ return 0.5 * (-Math.pow(2, -10 * --t) + 2);
+ },
+
+ easeInCirc: function(t) {
+ if (t >= 1) {
+ return t;
+ }
+ return -(Math.sqrt(1 - t * t) - 1);
+ },
+
+ easeOutCirc: function(t) {
+ return Math.sqrt(1 - (t = t - 1) * t);
+ },
+
+ easeInOutCirc: function(t) {
+ if ((t /= 0.5) < 1) {
+ return -0.5 * (Math.sqrt(1 - t * t) - 1);
+ }
+ return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
+ },
+
+ easeInElastic: function(t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) {
+ return 0;
+ }
+ if (t === 1) {
+ return 1;
+ }
+ if (!p) {
+ p = 0.3;
+ }
+ if (a < 1) {
+ a = 1;
+ s = p / 4;
+ } else {
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
+ }
+ return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
+ },
+
+ easeOutElastic: function(t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) {
+ return 0;
+ }
+ if (t === 1) {
+ return 1;
+ }
+ if (!p) {
+ p = 0.3;
+ }
+ if (a < 1) {
+ a = 1;
+ s = p / 4;
+ } else {
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
+ }
+ return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
+ },
+
+ easeInOutElastic: function(t) {
+ var s = 1.70158;
+ var p = 0;
+ var a = 1;
+ if (t === 0) {
+ return 0;
+ }
+ if ((t /= 0.5) === 2) {
+ return 1;
+ }
+ if (!p) {
+ p = 0.45;
+ }
+ if (a < 1) {
+ a = 1;
+ s = p / 4;
+ } else {
+ s = p / (2 * Math.PI) * Math.asin(1 / a);
+ }
+ if (t < 1) {
+ return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
+ }
+ return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
+ },
+ easeInBack: function(t) {
+ var s = 1.70158;
+ return t * t * ((s + 1) * t - s);
+ },
+
+ easeOutBack: function(t) {
+ var s = 1.70158;
+ return (t = t - 1) * t * ((s + 1) * t + s) + 1;
+ },
+
+ easeInOutBack: function(t) {
+ var s = 1.70158;
+ if ((t /= 0.5) < 1) {
+ return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
+ }
+ return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
+ },
+
+ easeInBounce: function(t) {
+ return 1 - effects.easeOutBounce(1 - t);
+ },
+
+ easeOutBounce: function(t) {
+ if (t < (1 / 2.75)) {
+ return 7.5625 * t * t;
+ }
+ if (t < (2 / 2.75)) {
+ return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
+ }
+ if (t < (2.5 / 2.75)) {
+ return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
+ }
+ return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
+ },
+
+ easeInOutBounce: function(t) {
+ if (t < 0.5) {
+ return effects.easeInBounce(t * 2) * 0.5;
+ }
+ return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
+ }
+};
+
+module.exports = {
+ effects: effects
+};
+
+// DEPRECATIONS
+
+/**
+ * Provided for backward compatibility, use Chart.helpers.easing.effects instead.
+ * @function Chart.helpers.easingEffects
+ * @deprecated since version 2.7.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.easingEffects = effects;
+
+},{"42":42}],44:[function(require,module,exports){
+'use strict';
+
+var helpers = require(42);
+
+/**
+ * @alias Chart.helpers.options
+ * @namespace
+ */
+module.exports = {
+ /**
+ * Converts the given line height `value` in pixels for a specific font `size`.
+ * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
+ * @param {Number} size - The font size (in pixels) used to resolve relative `value`.
+ * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid).
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
+ * @since 2.7.0
+ */
+ toLineHeight: function(value, size) {
+ var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
+ if (!matches || matches[1] === 'normal') {
+ return size * 1.2;
+ }
+
+ value = +matches[2];
+
+ switch (matches[3]) {
+ case 'px':
+ return value;
+ case '%':
+ value /= 100;
+ break;
+ default:
+ break;
+ }
+
+ return size * value;
+ },
+
+ /**
+ * Converts the given value into a padding object with pre-computed width/height.
+ * @param {Number|Object} value - If a number, set the value to all TRBL component,
+ * else, if and object, use defined properties and sets undefined ones to 0.
+ * @returns {Object} The padding values (top, right, bottom, left, width, height)
+ * @since 2.7.0
+ */
+ toPadding: function(value) {
+ var t, r, b, l;
+
+ if (helpers.isObject(value)) {
+ t = +value.top || 0;
+ r = +value.right || 0;
+ b = +value.bottom || 0;
+ l = +value.left || 0;
+ } else {
+ t = r = b = l = +value || 0;
+ }
+
+ return {
+ top: t,
+ right: r,
+ bottom: b,
+ left: l,
+ height: t + b,
+ width: l + r
+ };
+ },
+
+ /**
+ * Evaluates the given `inputs` sequentially and returns the first defined value.
+ * @param {Array[]} inputs - An array of values, falling back to the last value.
+ * @param {Object} [context] - If defined and the current value is a function, the value
+ * is called with `context` as first argument and the result becomes the new input.
+ * @param {Number} [index] - If defined and the current value is an array, the value
+ * at `index` become the new input.
+ * @since 2.7.0
+ */
+ resolve: function(inputs, context, index) {
+ var i, ilen, value;
+
+ for (i = 0, ilen = inputs.length; i < ilen; ++i) {
+ value = inputs[i];
+ if (value === undefined) {
+ continue;
+ }
+ if (context !== undefined && typeof value === 'function') {
+ value = value(context);
+ }
+ if (index !== undefined && helpers.isArray(value)) {
+ value = value[index];
+ }
+ if (value !== undefined) {
+ return value;
+ }
+ }
+ }
+};
+
+},{"42":42}],45:[function(require,module,exports){
+'use strict';
+
+module.exports = require(42);
+module.exports.easing = require(43);
+module.exports.canvas = require(41);
+module.exports.options = require(44);
+
+},{"41":41,"42":42,"43":43,"44":44}],46:[function(require,module,exports){
+/**
+ * Platform fallback implementation (minimal).
+ * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
+ */
+
+module.exports = {
+ acquireContext: function(item) {
+ if (item && item.canvas) {
+ // Support for any object associated to a canvas (including a context2d)
+ item = item.canvas;
+ }
+
+ return item && item.getContext('2d') || null;
+ }
+};
+
+},{}],47:[function(require,module,exports){
+/**
+ * Chart.Platform implementation for targeting a web browser
+ */
+
+'use strict';
+
+var helpers = require(45);
+
+var EXPANDO_KEY = '$chartjs';
+var CSS_PREFIX = 'chartjs-';
+var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
+var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
+var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
+
+/**
+ * DOM event types -> Chart.js event types.
+ * Note: only events with different types are mapped.
+ * @see https://developer.mozilla.org/en-US/docs/Web/Events
+ */
+var EVENT_TYPES = {
+ touchstart: 'mousedown',
+ touchmove: 'mousemove',
+ touchend: 'mouseup',
+ pointerenter: 'mouseenter',
+ pointerdown: 'mousedown',
+ pointermove: 'mousemove',
+ pointerup: 'mouseup',
+ pointerleave: 'mouseout',
+ pointerout: 'mouseout'
+};
+
+/**
+ * The "used" size is the final value of a dimension property after all calculations have
+ * been performed. This method uses the computed style of `element` but returns undefined
+ * if the computed style is not expressed in pixels. That can happen in some cases where
+ * `element` has a size relative to its parent and this last one is not yet displayed,
+ * for example because of `display: none` on a parent node.
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
+ * @returns {Number} Size in pixels or undefined if unknown.
+ */
+function readUsedSize(element, property) {
+ var value = helpers.getStyle(element, property);
+ var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
+ return matches ? Number(matches[1]) : undefined;
+}
+
+/**
+ * Initializes the canvas style and render size without modifying the canvas display size,
+ * since responsiveness is handled by the controller.resize() method. The config is used
+ * to determine the aspect ratio to apply in case no explicit height has been specified.
+ */
+function initCanvas(canvas, config) {
+ var style = canvas.style;
+
+ // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
+ // returns null or '' if no explicit value has been set to the canvas attribute.
+ var renderHeight = canvas.getAttribute('height');
+ var renderWidth = canvas.getAttribute('width');
+
+ // Chart.js modifies some canvas values that we want to restore on destroy
+ canvas[EXPANDO_KEY] = {
+ initial: {
+ height: renderHeight,
+ width: renderWidth,
+ style: {
+ display: style.display,
+ height: style.height,
+ width: style.width
+ }
+ }
+ };
+
+ // Force canvas to display as block to avoid extra space caused by inline
+ // elements, which would interfere with the responsive resize process.
+ // https://github.com/chartjs/Chart.js/issues/2538
+ style.display = style.display || 'block';
+
+ if (renderWidth === null || renderWidth === '') {
+ var displayWidth = readUsedSize(canvas, 'width');
+ if (displayWidth !== undefined) {
+ canvas.width = displayWidth;
+ }
+ }
+
+ if (renderHeight === null || renderHeight === '') {
+ if (canvas.style.height === '') {
+ // If no explicit render height and style height, let's apply the aspect ratio,
+ // which one can be specified by the user but also by charts as default option
+ // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
+ canvas.height = canvas.width / (config.options.aspectRatio || 2);
+ } else {
+ var displayHeight = readUsedSize(canvas, 'height');
+ if (displayWidth !== undefined) {
+ canvas.height = displayHeight;
+ }
+ }
+ }
+
+ return canvas;
+}
+
+/**
+ * Detects support for options object argument in addEventListener.
+ * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
+ * @private
+ */
+var supportsEventListenerOptions = (function() {
+ var supports = false;
+ try {
+ var options = Object.defineProperty({}, 'passive', {
+ get: function() {
+ supports = true;
+ }
+ });
+ window.addEventListener('e', null, options);
+ } catch (e) {
+ // continue regardless of error
+ }
+ return supports;
+}());
+
+// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
+// https://github.com/chartjs/Chart.js/issues/4287
+var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
+
+function addEventListener(node, type, listener) {
+ node.addEventListener(type, listener, eventListenerOptions);
+}
+
+function removeEventListener(node, type, listener) {
+ node.removeEventListener(type, listener, eventListenerOptions);
+}
+
+function createEvent(type, chart, x, y, nativeEvent) {
+ return {
+ type: type,
+ chart: chart,
+ native: nativeEvent || null,
+ x: x !== undefined ? x : null,
+ y: y !== undefined ? y : null,
+ };
+}
+
+function fromNativeEvent(event, chart) {
+ var type = EVENT_TYPES[event.type] || event.type;
+ var pos = helpers.getRelativePosition(event, chart);
+ return createEvent(type, chart, pos.x, pos.y, event);
+}
+
+function throttled(fn, thisArg) {
+ var ticking = false;
+ var args = [];
+
+ return function() {
+ args = Array.prototype.slice.call(arguments);
+ thisArg = thisArg || this;
+
+ if (!ticking) {
+ ticking = true;
+ helpers.requestAnimFrame.call(window, function() {
+ ticking = false;
+ fn.apply(thisArg, args);
+ });
+ }
+ };
+}
+
+// Implementation based on https://github.com/marcj/css-element-queries
+function createResizer(handler) {
+ var resizer = document.createElement('div');
+ var cls = CSS_PREFIX + 'size-monitor';
+ var maxSize = 1000000;
+ var style =
+ 'position:absolute;' +
+ 'left:0;' +
+ 'top:0;' +
+ 'right:0;' +
+ 'bottom:0;' +
+ 'overflow:hidden;' +
+ 'pointer-events:none;' +
+ 'visibility:hidden;' +
+ 'z-index:-1;';
+
+ resizer.style.cssText = style;
+ resizer.className = cls;
+ resizer.innerHTML =
+ '' +
+ '';
+
+ var expand = resizer.childNodes[0];
+ var shrink = resizer.childNodes[1];
+
+ resizer._reset = function() {
+ expand.scrollLeft = maxSize;
+ expand.scrollTop = maxSize;
+ shrink.scrollLeft = maxSize;
+ shrink.scrollTop = maxSize;
+ };
+ var onScroll = function() {
+ resizer._reset();
+ handler();
+ };
+
+ addEventListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
+ addEventListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
+
+ return resizer;
+}
+
+// https://davidwalsh.name/detect-node-insertion
+function watchForRender(node, handler) {
+ var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
+ var proxy = expando.renderProxy = function(e) {
+ if (e.animationName === CSS_RENDER_ANIMATION) {
+ handler();
+ }
+ };
+
+ helpers.each(ANIMATION_START_EVENTS, function(type) {
+ addEventListener(node, type, proxy);
+ });
+
+ // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
+ // is removed then added back immediately (same animation frame?). Accessing the
+ // `offsetParent` property will force a reflow and re-evaluate the CSS animation.
+ // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
+ // https://github.com/chartjs/Chart.js/issues/4737
+ expando.reflow = !!node.offsetParent;
+
+ node.classList.add(CSS_RENDER_MONITOR);
+}
+
+function unwatchForRender(node) {
+ var expando = node[EXPANDO_KEY] || {};
+ var proxy = expando.renderProxy;
+
+ if (proxy) {
+ helpers.each(ANIMATION_START_EVENTS, function(type) {
+ removeEventListener(node, type, proxy);
+ });
+
+ delete expando.renderProxy;
+ }
+
+ node.classList.remove(CSS_RENDER_MONITOR);
+}
+
+function addResizeListener(node, listener, chart) {
+ var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
+
+ // Let's keep track of this added resizer and thus avoid DOM query when removing it.
+ var resizer = expando.resizer = createResizer(throttled(function() {
+ if (expando.resizer) {
+ return listener(createEvent('resize', chart));
+ }
+ }));
+
+ // The resizer needs to be attached to the node parent, so we first need to be
+ // sure that `node` is attached to the DOM before injecting the resizer element.
+ watchForRender(node, function() {
+ if (expando.resizer) {
+ var container = node.parentNode;
+ if (container && container !== resizer.parentNode) {
+ container.insertBefore(resizer, container.firstChild);
+ }
+
+ // The container size might have changed, let's reset the resizer state.
+ resizer._reset();
+ }
+ });
+}
+
+function removeResizeListener(node) {
+ var expando = node[EXPANDO_KEY] || {};
+ var resizer = expando.resizer;
+
+ delete expando.resizer;
+ unwatchForRender(node);
+
+ if (resizer && resizer.parentNode) {
+ resizer.parentNode.removeChild(resizer);
+ }
+}
+
+function injectCSS(platform, css) {
+ // http://stackoverflow.com/q/3922139
+ var style = platform._style || document.createElement('style');
+ if (!platform._style) {
+ platform._style = style;
+ css = '/* Chart.js */\n' + css;
+ style.setAttribute('type', 'text/css');
+ document.getElementsByTagName('head')[0].appendChild(style);
+ }
+
+ style.appendChild(document.createTextNode(css));
+}
+
+module.exports = {
+ /**
+ * This property holds whether this platform is enabled for the current environment.
+ * Currently used by platform.js to select the proper implementation.
+ * @private
+ */
+ _enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
+
+ initialize: function() {
+ var keyframes = 'from{opacity:0.99}to{opacity:1}';
+
+ injectCSS(this,
+ // DOM rendering detection
+ // https://davidwalsh.name/detect-node-insertion
+ '@-webkit-keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
+ '@keyframes ' + CSS_RENDER_ANIMATION + '{' + keyframes + '}' +
+ '.' + CSS_RENDER_MONITOR + '{' +
+ '-webkit-animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
+ 'animation:' + CSS_RENDER_ANIMATION + ' 0.001s;' +
+ '}'
+ );
+ },
+
+ acquireContext: function(item, config) {
+ if (typeof item === 'string') {
+ item = document.getElementById(item);
+ } else if (item.length) {
+ // Support for array based queries (such as jQuery)
+ item = item[0];
+ }
+
+ if (item && item.canvas) {
+ // Support for any object associated to a canvas (including a context2d)
+ item = item.canvas;
+ }
+
+ // To prevent canvas fingerprinting, some add-ons undefine the getContext
+ // method, for example: https://github.com/kkapsner/CanvasBlocker
+ // https://github.com/chartjs/Chart.js/issues/2807
+ var context = item && item.getContext && item.getContext('2d');
+
+ // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
+ // inside an iframe or when running in a protected environment. We could guess the
+ // types from their toString() value but let's keep things flexible and assume it's
+ // a sufficient condition if the item has a context2D which has item as `canvas`.
+ // https://github.com/chartjs/Chart.js/issues/3887
+ // https://github.com/chartjs/Chart.js/issues/4102
+ // https://github.com/chartjs/Chart.js/issues/4152
+ if (context && context.canvas === item) {
+ initCanvas(item, config);
+ return context;
+ }
+
+ return null;
+ },
+
+ releaseContext: function(context) {
+ var canvas = context.canvas;
+ if (!canvas[EXPANDO_KEY]) {
+ return;
+ }
+
+ var initial = canvas[EXPANDO_KEY].initial;
+ ['height', 'width'].forEach(function(prop) {
+ var value = initial[prop];
+ if (helpers.isNullOrUndef(value)) {
+ canvas.removeAttribute(prop);
+ } else {
+ canvas.setAttribute(prop, value);
+ }
+ });
+
+ helpers.each(initial.style || {}, function(value, key) {
+ canvas.style[key] = value;
+ });
+
+ // The canvas render size might have been changed (and thus the state stack discarded),
+ // we can't use save() and restore() to restore the initial state. So make sure that at
+ // least the canvas context is reset to the default state by setting the canvas width.
+ // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
+ canvas.width = canvas.width;
+
+ delete canvas[EXPANDO_KEY];
+ },
+
+ addEventListener: function(chart, type, listener) {
+ var canvas = chart.canvas;
+ if (type === 'resize') {
+ // Note: the resize event is not supported on all browsers.
+ addResizeListener(canvas, listener, chart);
+ return;
+ }
+
+ var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
+ var proxies = expando.proxies || (expando.proxies = {});
+ var proxy = proxies[chart.id + '_' + type] = function(event) {
+ listener(fromNativeEvent(event, chart));
+ };
+
+ addEventListener(canvas, type, proxy);
+ },
+
+ removeEventListener: function(chart, type, listener) {
+ var canvas = chart.canvas;
+ if (type === 'resize') {
+ // Note: the resize event is not supported on all browsers.
+ removeResizeListener(canvas, listener);
+ return;
+ }
+
+ var expando = listener[EXPANDO_KEY] || {};
+ var proxies = expando.proxies || {};
+ var proxy = proxies[chart.id + '_' + type];
+ if (!proxy) {
+ return;
+ }
+
+ removeEventListener(canvas, type, proxy);
+ }
+};
+
+// DEPRECATIONS
+
+/**
+ * Provided for backward compatibility, use EventTarget.addEventListener instead.
+ * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
+ * @function Chart.helpers.addEvent
+ * @deprecated since version 2.7.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.addEvent = addEventListener;
+
+/**
+ * Provided for backward compatibility, use EventTarget.removeEventListener instead.
+ * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
+ * @function Chart.helpers.removeEvent
+ * @deprecated since version 2.7.0
+ * @todo remove at version 3
+ * @private
+ */
+helpers.removeEvent = removeEventListener;
+
+},{"45":45}],48:[function(require,module,exports){
+'use strict';
+
+var helpers = require(45);
+var basic = require(46);
+var dom = require(47);
+
+// @TODO Make possible to select another platform at build time.
+var implementation = dom._enabled ? dom : basic;
+
+/**
+ * @namespace Chart.platform
+ * @see https://chartjs.gitbooks.io/proposals/content/Platform.html
+ * @since 2.4.0
+ */
+module.exports = helpers.extend({
+ /**
+ * @since 2.7.0
+ */
+ initialize: function() {},
+
+ /**
+ * Called at chart construction time, returns a context2d instance implementing
+ * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
+ * @param {*} item - The native item from which to acquire context (platform specific)
+ * @param {Object} options - The chart options
+ * @returns {CanvasRenderingContext2D} context2d instance
+ */
+ acquireContext: function() {},
+
+ /**
+ * Called at chart destruction time, releases any resources associated to the context
+ * previously returned by the acquireContext() method.
+ * @param {CanvasRenderingContext2D} context - The context2d instance
+ * @returns {Boolean} true if the method succeeded, else false
+ */
+ releaseContext: function() {},
+
+ /**
+ * Registers the specified listener on the given chart.
+ * @param {Chart} chart - Chart from which to listen for event
+ * @param {String} type - The ({@link IEvent}) type to listen for
+ * @param {Function} listener - Receives a notification (an object that implements
+ * the {@link IEvent} interface) when an event of the specified type occurs.
+ */
+ addEventListener: function() {},
+
+ /**
+ * Removes the specified listener previously registered with addEventListener.
+ * @param {Chart} chart -Chart from which to remove the listener
+ * @param {String} type - The ({@link IEvent}) type to remove
+ * @param {Function} listener - The listener function to remove from the event target.
+ */
+ removeEventListener: function() {}
+
+}, implementation);
+
+/**
+ * @interface IPlatform
+ * Allows abstracting platform dependencies away from the chart
+ * @borrows Chart.platform.acquireContext as acquireContext
+ * @borrows Chart.platform.releaseContext as releaseContext
+ * @borrows Chart.platform.addEventListener as addEventListener
+ * @borrows Chart.platform.removeEventListener as removeEventListener
+ */
+
+/**
+ * @interface IEvent
+ * @prop {String} type - The event type name, possible values are:
+ * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout',
+ * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize'
+ * @prop {*} native - The original native event (null for emulated events, e.g. 'resize')
+ * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events)
+ * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events)
+ */
+
+},{"45":45,"46":46,"47":47}],49:[function(require,module,exports){
+/**
+ * Plugin based on discussion from the following Chart.js issues:
+ * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569
+ * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897
+ */
+
+'use strict';
+
+var defaults = require(25);
+var elements = require(40);
+var helpers = require(45);
+
+defaults._set('global', {
+ plugins: {
+ filler: {
+ propagate: true
+ }
+ }
+});
+
+module.exports = function() {
+
+ var mappers = {
+ dataset: function(source) {
+ var index = source.fill;
+ var chart = source.chart;
+ var meta = chart.getDatasetMeta(index);
+ var visible = meta && chart.isDatasetVisible(index);
+ var points = (visible && meta.dataset._children) || [];
+ var length = points.length || 0;
+
+ return !length ? null : function(point, i) {
+ return (i < length && points[i]._view) || null;
+ };
+ },
+
+ boundary: function(source) {
+ var boundary = source.boundary;
+ var x = boundary ? boundary.x : null;
+ var y = boundary ? boundary.y : null;
+
+ return function(point) {
+ return {
+ x: x === null ? point.x : x,
+ y: y === null ? point.y : y,
+ };
+ };
+ }
+ };
+
+ // @todo if (fill[0] === '#')
+ function decodeFill(el, index, count) {
+ var model = el._model || {};
+ var fill = model.fill;
+ var target;
+
+ if (fill === undefined) {
+ fill = !!model.backgroundColor;
+ }
+
+ if (fill === false || fill === null) {
+ return false;
+ }
+
+ if (fill === true) {
+ return 'origin';
+ }
+
+ target = parseFloat(fill, 10);
+ if (isFinite(target) && Math.floor(target) === target) {
+ if (fill[0] === '-' || fill[0] === '+') {
+ target = index + target;
+ }
+
+ if (target === index || target < 0 || target >= count) {
+ return false;
+ }
+
+ return target;
+ }
+
+ switch (fill) {
+ // compatibility
+ case 'bottom':
+ return 'start';
+ case 'top':
+ return 'end';
+ case 'zero':
+ return 'origin';
+ // supported boundaries
+ case 'origin':
+ case 'start':
+ case 'end':
+ return fill;
+ // invalid fill values
+ default:
+ return false;
+ }
+ }
+
+ function computeBoundary(source) {
+ var model = source.el._model || {};
+ var scale = source.el._scale || {};
+ var fill = source.fill;
+ var target = null;
+ var horizontal;
+
+ if (isFinite(fill)) {
+ return null;
+ }
+
+ // Backward compatibility: until v3, we still need to support boundary values set on
+ // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
+ // controllers might still use it (e.g. the Smith chart).
+
+ if (fill === 'start') {
+ target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
+ } else if (fill === 'end') {
+ target = model.scaleTop === undefined ? scale.top : model.scaleTop;
+ } else if (model.scaleZero !== undefined) {
+ target = model.scaleZero;
+ } else if (scale.getBasePosition) {
+ target = scale.getBasePosition();
+ } else if (scale.getBasePixel) {
+ target = scale.getBasePixel();
+ }
+
+ if (target !== undefined && target !== null) {
+ if (target.x !== undefined && target.y !== undefined) {
+ return target;
+ }
+
+ if (typeof target === 'number' && isFinite(target)) {
+ horizontal = scale.isHorizontal();
+ return {
+ x: horizontal ? target : null,
+ y: horizontal ? null : target
+ };
+ }
+ }
+
+ return null;
+ }
+
+ function resolveTarget(sources, index, propagate) {
+ var source = sources[index];
+ var fill = source.fill;
+ var visited = [index];
+ var target;
+
+ if (!propagate) {
+ return fill;
+ }
+
+ while (fill !== false && visited.indexOf(fill) === -1) {
+ if (!isFinite(fill)) {
+ return fill;
+ }
+
+ target = sources[fill];
+ if (!target) {
+ return false;
+ }
+
+ if (target.visible) {
+ return fill;
+ }
+
+ visited.push(fill);
+ fill = target.fill;
+ }
+
+ return false;
+ }
+
+ function createMapper(source) {
+ var fill = source.fill;
+ var type = 'dataset';
+
+ if (fill === false) {
+ return null;
+ }
+
+ if (!isFinite(fill)) {
+ type = 'boundary';
+ }
+
+ return mappers[type](source);
+ }
+
+ function isDrawable(point) {
+ return point && !point.skip;
+ }
+
+ function drawArea(ctx, curve0, curve1, len0, len1) {
+ var i;
+
+ if (!len0 || !len1) {
+ return;
+ }
+
+ // building first area curve (normal)
+ ctx.moveTo(curve0[0].x, curve0[0].y);
+ for (i = 1; i < len0; ++i) {
+ helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
+ }
+
+ // joining the two area curves
+ ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
+
+ // building opposite area curve (reverse)
+ for (i = len1 - 1; i > 0; --i) {
+ helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
+ }
+ }
+
+ function doFill(ctx, points, mapper, view, color, loop) {
+ var count = points.length;
+ var span = view.spanGaps;
+ var curve0 = [];
+ var curve1 = [];
+ var len0 = 0;
+ var len1 = 0;
+ var i, ilen, index, p0, p1, d0, d1;
+
+ ctx.beginPath();
+
+ for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
+ index = i % count;
+ p0 = points[index]._view;
+ p1 = mapper(p0, index, view);
+ d0 = isDrawable(p0);
+ d1 = isDrawable(p1);
+
+ if (d0 && d1) {
+ len0 = curve0.push(p0);
+ len1 = curve1.push(p1);
+ } else if (len0 && len1) {
+ if (!span) {
+ drawArea(ctx, curve0, curve1, len0, len1);
+ len0 = len1 = 0;
+ curve0 = [];
+ curve1 = [];
+ } else {
+ if (d0) {
+ curve0.push(p0);
+ }
+ if (d1) {
+ curve1.push(p1);
+ }
+ }
+ }
+ }
+
+ drawArea(ctx, curve0, curve1, len0, len1);
+
+ ctx.closePath();
+ ctx.fillStyle = color;
+ ctx.fill();
+ }
+
+ return {
+ id: 'filler',
+
+ afterDatasetsUpdate: function(chart, options) {
+ var count = (chart.data.datasets || []).length;
+ var propagate = options.propagate;
+ var sources = [];
+ var meta, i, el, source;
+
+ for (i = 0; i < count; ++i) {
+ meta = chart.getDatasetMeta(i);
+ el = meta.dataset;
+ source = null;
+
+ if (el && el._model && el instanceof elements.Line) {
+ source = {
+ visible: chart.isDatasetVisible(i),
+ fill: decodeFill(el, i, count),
+ chart: chart,
+ el: el
+ };
+ }
+
+ meta.$filler = source;
+ sources.push(source);
+ }
+
+ for (i = 0; i < count; ++i) {
+ source = sources[i];
+ if (!source) {
+ continue;
+ }
+
+ source.fill = resolveTarget(sources, i, propagate);
+ source.boundary = computeBoundary(source);
+ source.mapper = createMapper(source);
+ }
+ },
+
+ beforeDatasetDraw: function(chart, args) {
+ var meta = args.meta.$filler;
+ if (!meta) {
+ return;
+ }
+
+ var ctx = chart.ctx;
+ var el = meta.el;
+ var view = el._view;
+ var points = el._children || [];
+ var mapper = meta.mapper;
+ var color = view.backgroundColor || defaults.global.defaultColor;
+
+ if (mapper && color && points.length) {
+ helpers.canvas.clipArea(ctx, chart.chartArea);
+ doFill(ctx, points, mapper, view, color, el._loop);
+ helpers.canvas.unclipArea(ctx);
+ }
+ }
+ };
+};
+
+},{"25":25,"40":40,"45":45}],50:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+
+defaults._set('global', {
+ legend: {
+ display: true,
+ position: 'top',
+ fullWidth: true,
+ reverse: false,
+ weight: 1000,
+
+ // a callback that will handle
+ onClick: function(e, legendItem) {
+ var index = legendItem.datasetIndex;
+ var ci = this.chart;
+ var meta = ci.getDatasetMeta(index);
+
+ // See controller.isDatasetVisible comment
+ meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
+
+ // We hid a dataset ... rerender the chart
+ ci.update();
+ },
+
+ onHover: null,
+
+ labels: {
+ boxWidth: 40,
+ padding: 10,
+ // Generates labels shown in the legend
+ // Valid properties to return:
+ // text : text to display
+ // fillStyle : fill of coloured box
+ // strokeStyle: stroke of coloured box
+ // hidden : if this legend item refers to a hidden item
+ // lineCap : cap style for line
+ // lineDash
+ // lineDashOffset :
+ // lineJoin :
+ // lineWidth :
+ generateLabels: function(chart) {
+ var data = chart.data;
+ return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
+ return {
+ text: dataset.label,
+ fillStyle: (!helpers.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
+ hidden: !chart.isDatasetVisible(i),
+ lineCap: dataset.borderCapStyle,
+ lineDash: dataset.borderDash,
+ lineDashOffset: dataset.borderDashOffset,
+ lineJoin: dataset.borderJoinStyle,
+ lineWidth: dataset.borderWidth,
+ strokeStyle: dataset.borderColor,
+ pointStyle: dataset.pointStyle,
+
+ // Below is extra data used for toggling the datasets
+ datasetIndex: i
+ };
+ }, this) : [];
+ }
+ }
+ },
+
+ legendCallback: function(chart) {
+ var text = [];
+ text.push('');
+ for (var i = 0; i < chart.data.datasets.length; i++) {
+ text.push(' ');
+ if (chart.data.datasets[i].label) {
+ text.push(chart.data.datasets[i].label);
+ }
+ text.push(' ');
+ }
+ text.push(' ');
+ return text.join('');
+ }
+});
+
+module.exports = function(Chart) {
+
+ var layout = Chart.layoutService;
+ var noop = helpers.noop;
+
+ /**
+ * Helper function to get the box width based on the usePointStyle option
+ * @param labelopts {Object} the label options on the legend
+ * @param fontSize {Number} the label font size
+ * @return {Number} width of the color box area
+ */
+ function getBoxWidth(labelOpts, fontSize) {
+ return labelOpts.usePointStyle ?
+ fontSize * Math.SQRT2 :
+ labelOpts.boxWidth;
+ }
+
+ Chart.Legend = Element.extend({
+
+ initialize: function(config) {
+ helpers.extend(this, config);
+
+ // Contains hit boxes for each dataset (in dataset order)
+ this.legendHitBoxes = [];
+
+ // Are we in doughnut mode which has a different data type
+ this.doughnutMode = false;
+ },
+
+ // These methods are ordered by lifecycle. Utilities then follow.
+ // Any function defined here is inherited by all legend types.
+ // Any function can be extended by the legend type
+
+ beforeUpdate: noop,
+ update: function(maxWidth, maxHeight, margins) {
+ var me = this;
+
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
+ me.beforeUpdate();
+
+ // Absorb the master measurements
+ me.maxWidth = maxWidth;
+ me.maxHeight = maxHeight;
+ me.margins = margins;
+
+ // Dimensions
+ me.beforeSetDimensions();
+ me.setDimensions();
+ me.afterSetDimensions();
+ // Labels
+ me.beforeBuildLabels();
+ me.buildLabels();
+ me.afterBuildLabels();
+
+ // Fit
+ me.beforeFit();
+ me.fit();
+ me.afterFit();
+ //
+ me.afterUpdate();
+
+ return me.minSize;
+ },
+ afterUpdate: noop,
+
+ //
+
+ beforeSetDimensions: noop,
+ setDimensions: function() {
+ var me = this;
+ // Set the unconstrained dimension before label rotation
+ if (me.isHorizontal()) {
+ // Reset position before calculating rotation
+ me.width = me.maxWidth;
+ me.left = 0;
+ me.right = me.width;
+ } else {
+ me.height = me.maxHeight;
+
+ // Reset position before calculating rotation
+ me.top = 0;
+ me.bottom = me.height;
+ }
+
+ // Reset padding
+ me.paddingLeft = 0;
+ me.paddingTop = 0;
+ me.paddingRight = 0;
+ me.paddingBottom = 0;
+
+ // Reset minSize
+ me.minSize = {
+ width: 0,
+ height: 0
+ };
+ },
+ afterSetDimensions: noop,
+
+ //
+
+ beforeBuildLabels: noop,
+ buildLabels: function() {
+ var me = this;
+ var labelOpts = me.options.labels || {};
+ var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
+
+ if (labelOpts.filter) {
+ legendItems = legendItems.filter(function(item) {
+ return labelOpts.filter(item, me.chart.data);
+ });
+ }
+
+ if (me.options.reverse) {
+ legendItems.reverse();
+ }
+
+ me.legendItems = legendItems;
+ },
+ afterBuildLabels: noop,
+
+ //
+
+ beforeFit: noop,
+ fit: function() {
+ var me = this;
+ var opts = me.options;
+ var labelOpts = opts.labels;
+ var display = opts.display;
+
+ var ctx = me.ctx;
+
+ var globalDefault = defaults.global;
+ var valueOrDefault = helpers.valueOrDefault;
+ var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
+ var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
+ var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
+ var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
+
+ // Reset hit boxes
+ var hitboxes = me.legendHitBoxes = [];
+
+ var minSize = me.minSize;
+ var isHorizontal = me.isHorizontal();
+
+ if (isHorizontal) {
+ minSize.width = me.maxWidth; // fill all the width
+ minSize.height = display ? 10 : 0;
+ } else {
+ minSize.width = display ? 10 : 0;
+ minSize.height = me.maxHeight; // fill all the height
+ }
+
+ // Increase sizes here
+ if (display) {
+ ctx.font = labelFont;
+
+ if (isHorizontal) {
+ // Labels
+
+ // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
+ var lineWidths = me.lineWidths = [0];
+ var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
+
+ ctx.textAlign = 'left';
+ ctx.textBaseline = 'top';
+
+ helpers.each(me.legendItems, function(legendItem, i) {
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
+ var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
+
+ if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
+ totalHeight += fontSize + (labelOpts.padding);
+ lineWidths[lineWidths.length] = me.left;
+ }
+
+ // Store the hitbox width and height here. Final position will be updated in `draw`
+ hitboxes[i] = {
+ left: 0,
+ top: 0,
+ width: width,
+ height: fontSize
+ };
+
+ lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
+ });
+
+ minSize.height += totalHeight;
+
+ } else {
+ var vPadding = labelOpts.padding;
+ var columnWidths = me.columnWidths = [];
+ var totalWidth = labelOpts.padding;
+ var currentColWidth = 0;
+ var currentColHeight = 0;
+ var itemHeight = fontSize + vPadding;
+
+ helpers.each(me.legendItems, function(legendItem, i) {
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
+ var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
+
+ // If too tall, go to new column
+ if (currentColHeight + itemHeight > minSize.height) {
+ totalWidth += currentColWidth + labelOpts.padding;
+ columnWidths.push(currentColWidth); // previous column width
+
+ currentColWidth = 0;
+ currentColHeight = 0;
+ }
+
+ // Get max width
+ currentColWidth = Math.max(currentColWidth, itemWidth);
+ currentColHeight += itemHeight;
+
+ // Store the hitbox width and height here. Final position will be updated in `draw`
+ hitboxes[i] = {
+ left: 0,
+ top: 0,
+ width: itemWidth,
+ height: fontSize
+ };
+ });
+
+ totalWidth += currentColWidth;
+ columnWidths.push(currentColWidth);
+ minSize.width += totalWidth;
+ }
+ }
+
+ me.width = minSize.width;
+ me.height = minSize.height;
+ },
+ afterFit: noop,
+
+ // Shared Methods
+ isHorizontal: function() {
+ return this.options.position === 'top' || this.options.position === 'bottom';
+ },
+
+ // Actually draw the legend on the canvas
+ draw: function() {
+ var me = this;
+ var opts = me.options;
+ var labelOpts = opts.labels;
+ var globalDefault = defaults.global;
+ var lineDefault = globalDefault.elements.line;
+ var legendWidth = me.width;
+ var lineWidths = me.lineWidths;
+
+ if (opts.display) {
+ var ctx = me.ctx;
+ var valueOrDefault = helpers.valueOrDefault;
+ var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
+ var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
+ var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
+ var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
+ var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
+ var cursor;
+
+ // Canvas setup
+ ctx.textAlign = 'left';
+ ctx.textBaseline = 'middle';
+ ctx.lineWidth = 0.5;
+ ctx.strokeStyle = fontColor; // for strikethrough effect
+ ctx.fillStyle = fontColor; // render in correct colour
+ ctx.font = labelFont;
+
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
+ var hitboxes = me.legendHitBoxes;
+
+ // current position
+ var drawLegendBox = function(x, y, legendItem) {
+ if (isNaN(boxWidth) || boxWidth <= 0) {
+ return;
+ }
+
+ // Set the ctx for the box
+ ctx.save();
+
+ ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
+ ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
+ ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
+ ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
+ ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
+ ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
+ var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
+
+ if (ctx.setLineDash) {
+ // IE 9 and 10 do not support line dash
+ ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
+ }
+
+ if (opts.labels && opts.labels.usePointStyle) {
+ // Recalculate x and y for drawPoint() because its expecting
+ // x and y to be center of figure (instead of top left)
+ var radius = fontSize * Math.SQRT2 / 2;
+ var offSet = radius / Math.SQRT2;
+ var centerX = x + offSet;
+ var centerY = y + offSet;
+
+ // Draw pointStyle as legend symbol
+ helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
+ } else {
+ // Draw box as legend symbol
+ if (!isLineWidthZero) {
+ ctx.strokeRect(x, y, boxWidth, fontSize);
+ }
+ ctx.fillRect(x, y, boxWidth, fontSize);
+ }
+
+ ctx.restore();
+ };
+ var fillText = function(x, y, legendItem, textWidth) {
+ var halfFontSize = fontSize / 2;
+ var xLeft = boxWidth + halfFontSize + x;
+ var yMiddle = y + halfFontSize;
+
+ ctx.fillText(legendItem.text, xLeft, yMiddle);
+
+ if (legendItem.hidden) {
+ // Strikethrough the text if hidden
+ ctx.beginPath();
+ ctx.lineWidth = 2;
+ ctx.moveTo(xLeft, yMiddle);
+ ctx.lineTo(xLeft + textWidth, yMiddle);
+ ctx.stroke();
+ }
+ };
+
+ // Horizontal
+ var isHorizontal = me.isHorizontal();
+ if (isHorizontal) {
+ cursor = {
+ x: me.left + ((legendWidth - lineWidths[0]) / 2),
+ y: me.top + labelOpts.padding,
+ line: 0
+ };
+ } else {
+ cursor = {
+ x: me.left + labelOpts.padding,
+ y: me.top + labelOpts.padding,
+ line: 0
+ };
+ }
+
+ var itemHeight = fontSize + labelOpts.padding;
+ helpers.each(me.legendItems, function(legendItem, i) {
+ var textWidth = ctx.measureText(legendItem.text).width;
+ var width = boxWidth + (fontSize / 2) + textWidth;
+ var x = cursor.x;
+ var y = cursor.y;
+
+ if (isHorizontal) {
+ if (x + width >= legendWidth) {
+ y = cursor.y += itemHeight;
+ cursor.line++;
+ x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
+ }
+ } else if (y + itemHeight > me.bottom) {
+ x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
+ y = cursor.y = me.top + labelOpts.padding;
+ cursor.line++;
+ }
+
+ drawLegendBox(x, y, legendItem);
+
+ hitboxes[i].left = x;
+ hitboxes[i].top = y;
+
+ // Fill the actual label
+ fillText(x, y, legendItem, textWidth);
+
+ if (isHorizontal) {
+ cursor.x += width + (labelOpts.padding);
+ } else {
+ cursor.y += itemHeight;
+ }
+
+ });
+ }
+ },
+
+ /**
+ * Handle an event
+ * @private
+ * @param {IEvent} event - The event to handle
+ * @return {Boolean} true if a change occured
+ */
+ handleEvent: function(e) {
+ var me = this;
+ var opts = me.options;
+ var type = e.type === 'mouseup' ? 'click' : e.type;
+ var changed = false;
+
+ if (type === 'mousemove') {
+ if (!opts.onHover) {
+ return;
+ }
+ } else if (type === 'click') {
+ if (!opts.onClick) {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ // Chart event already has relative position in it
+ var x = e.x;
+ var y = e.y;
+
+ if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
+ // See if we are touching one of the dataset boxes
+ var lh = me.legendHitBoxes;
+ for (var i = 0; i < lh.length; ++i) {
+ var hitBox = lh[i];
+
+ if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
+ // Touching an element
+ if (type === 'click') {
+ // use e.native for backwards compatibility
+ opts.onClick.call(me, e.native, me.legendItems[i]);
+ changed = true;
+ break;
+ } else if (type === 'mousemove') {
+ // use e.native for backwards compatibility
+ opts.onHover.call(me, e.native, me.legendItems[i]);
+ changed = true;
+ break;
+ }
+ }
+ }
+ }
+
+ return changed;
+ }
+ });
+
+ function createNewLegendAndAttach(chart, legendOpts) {
+ var legend = new Chart.Legend({
+ ctx: chart.ctx,
+ options: legendOpts,
+ chart: chart
+ });
+
+ layout.configure(chart, legend, legendOpts);
+ layout.addBox(chart, legend);
+ chart.legend = legend;
+ }
+
+ return {
+ id: 'legend',
+
+ beforeInit: function(chart) {
+ var legendOpts = chart.options.legend;
+
+ if (legendOpts) {
+ createNewLegendAndAttach(chart, legendOpts);
+ }
+ },
+
+ beforeUpdate: function(chart) {
+ var legendOpts = chart.options.legend;
+ var legend = chart.legend;
+
+ if (legendOpts) {
+ helpers.mergeIf(legendOpts, defaults.global.legend);
+
+ if (legend) {
+ layout.configure(chart, legend, legendOpts);
+ legend.options = legendOpts;
+ } else {
+ createNewLegendAndAttach(chart, legendOpts);
+ }
+ } else if (legend) {
+ layout.removeBox(chart, legend);
+ delete chart.legend;
+ }
+ },
+
+ afterEvent: function(chart, e) {
+ var legend = chart.legend;
+ if (legend) {
+ legend.handleEvent(e);
+ }
+ }
+ };
+};
+
+},{"25":25,"26":26,"45":45}],51:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var Element = require(26);
+var helpers = require(45);
+
+defaults._set('global', {
+ title: {
+ display: false,
+ fontStyle: 'bold',
+ fullWidth: true,
+ lineHeight: 1.2,
+ padding: 10,
+ position: 'top',
+ text: '',
+ weight: 2000 // by default greater than legend (1000) to be above
+ }
+});
+
+module.exports = function(Chart) {
+
+ var layout = Chart.layoutService;
+ var noop = helpers.noop;
+
+ Chart.Title = Element.extend({
+ initialize: function(config) {
+ var me = this;
+ helpers.extend(me, config);
+
+ // Contains hit boxes for each dataset (in dataset order)
+ me.legendHitBoxes = [];
+ },
+
+ // These methods are ordered by lifecycle. Utilities then follow.
+
+ beforeUpdate: noop,
+ update: function(maxWidth, maxHeight, margins) {
+ var me = this;
+
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
+ me.beforeUpdate();
+
+ // Absorb the master measurements
+ me.maxWidth = maxWidth;
+ me.maxHeight = maxHeight;
+ me.margins = margins;
+
+ // Dimensions
+ me.beforeSetDimensions();
+ me.setDimensions();
+ me.afterSetDimensions();
+ // Labels
+ me.beforeBuildLabels();
+ me.buildLabels();
+ me.afterBuildLabels();
+
+ // Fit
+ me.beforeFit();
+ me.fit();
+ me.afterFit();
+ //
+ me.afterUpdate();
+
+ return me.minSize;
+
+ },
+ afterUpdate: noop,
+
+ //
+
+ beforeSetDimensions: noop,
+ setDimensions: function() {
+ var me = this;
+ // Set the unconstrained dimension before label rotation
+ if (me.isHorizontal()) {
+ // Reset position before calculating rotation
+ me.width = me.maxWidth;
+ me.left = 0;
+ me.right = me.width;
+ } else {
+ me.height = me.maxHeight;
+
+ // Reset position before calculating rotation
+ me.top = 0;
+ me.bottom = me.height;
+ }
+
+ // Reset padding
+ me.paddingLeft = 0;
+ me.paddingTop = 0;
+ me.paddingRight = 0;
+ me.paddingBottom = 0;
+
+ // Reset minSize
+ me.minSize = {
+ width: 0,
+ height: 0
+ };
+ },
+ afterSetDimensions: noop,
+
+ //
+
+ beforeBuildLabels: noop,
+ buildLabels: noop,
+ afterBuildLabels: noop,
+
+ //
+
+ beforeFit: noop,
+ fit: function() {
+ var me = this;
+ var valueOrDefault = helpers.valueOrDefault;
+ var opts = me.options;
+ var display = opts.display;
+ var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
+ var minSize = me.minSize;
+ var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
+ var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
+ var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
+
+ if (me.isHorizontal()) {
+ minSize.width = me.maxWidth; // fill all the width
+ minSize.height = textSize;
+ } else {
+ minSize.width = textSize;
+ minSize.height = me.maxHeight; // fill all the height
+ }
+
+ me.width = minSize.width;
+ me.height = minSize.height;
+
+ },
+ afterFit: noop,
+
+ // Shared Methods
+ isHorizontal: function() {
+ var pos = this.options.position;
+ return pos === 'top' || pos === 'bottom';
+ },
+
+ // Actually draw the title block on the canvas
+ draw: function() {
+ var me = this;
+ var ctx = me.ctx;
+ var valueOrDefault = helpers.valueOrDefault;
+ var opts = me.options;
+ var globalDefaults = defaults.global;
+
+ if (opts.display) {
+ var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
+ var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
+ var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
+ var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
+ var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
+ var offset = lineHeight / 2 + opts.padding;
+ var rotation = 0;
+ var top = me.top;
+ var left = me.left;
+ var bottom = me.bottom;
+ var right = me.right;
+ var maxWidth, titleX, titleY;
+
+ ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
+ ctx.font = titleFont;
+
+ // Horizontal
+ if (me.isHorizontal()) {
+ titleX = left + ((right - left) / 2); // midpoint of the width
+ titleY = top + offset;
+ maxWidth = right - left;
+ } else {
+ titleX = opts.position === 'left' ? left + offset : right - offset;
+ titleY = top + ((bottom - top) / 2);
+ maxWidth = bottom - top;
+ rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
+ }
+
+ ctx.save();
+ ctx.translate(titleX, titleY);
+ ctx.rotate(rotation);
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+
+ var text = opts.text;
+ if (helpers.isArray(text)) {
+ var y = 0;
+ for (var i = 0; i < text.length; ++i) {
+ ctx.fillText(text[i], 0, y, maxWidth);
+ y += lineHeight;
+ }
+ } else {
+ ctx.fillText(text, 0, 0, maxWidth);
+ }
+
+ ctx.restore();
+ }
+ }
+ });
+
+ function createNewTitleBlockAndAttach(chart, titleOpts) {
+ var title = new Chart.Title({
+ ctx: chart.ctx,
+ options: titleOpts,
+ chart: chart
+ });
+
+ layout.configure(chart, title, titleOpts);
+ layout.addBox(chart, title);
+ chart.titleBlock = title;
+ }
+
+ return {
+ id: 'title',
+
+ beforeInit: function(chart) {
+ var titleOpts = chart.options.title;
+
+ if (titleOpts) {
+ createNewTitleBlockAndAttach(chart, titleOpts);
+ }
+ },
+
+ beforeUpdate: function(chart) {
+ var titleOpts = chart.options.title;
+ var titleBlock = chart.titleBlock;
+
+ if (titleOpts) {
+ helpers.mergeIf(titleOpts, defaults.global.title);
+
+ if (titleBlock) {
+ layout.configure(chart, titleBlock, titleOpts);
+ titleBlock.options = titleOpts;
+ } else {
+ createNewTitleBlockAndAttach(chart, titleOpts);
+ }
+ } else if (titleBlock) {
+ Chart.layoutService.removeBox(chart, titleBlock);
+ delete chart.titleBlock;
+ }
+ }
+ };
+};
+
+},{"25":25,"26":26,"45":45}],52:[function(require,module,exports){
+'use strict';
+
+module.exports = function(Chart) {
+
+ // Default config for a category scale
+ var defaultConfig = {
+ position: 'bottom'
+ };
+
+ var DatasetScale = Chart.Scale.extend({
+ /**
+ * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
+ * else fall back to data.labels
+ * @private
+ */
+ getLabels: function() {
+ var data = this.chart.data;
+ return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
+ },
+
+ determineDataLimits: function() {
+ var me = this;
+ var labels = me.getLabels();
+ me.minIndex = 0;
+ me.maxIndex = labels.length - 1;
+ var findIndex;
+
+ if (me.options.ticks.min !== undefined) {
+ // user specified min value
+ findIndex = labels.indexOf(me.options.ticks.min);
+ me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
+ }
+
+ if (me.options.ticks.max !== undefined) {
+ // user specified max value
+ findIndex = labels.indexOf(me.options.ticks.max);
+ me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
+ }
+
+ me.min = labels[me.minIndex];
+ me.max = labels[me.maxIndex];
+ },
+
+ buildTicks: function() {
+ var me = this;
+ var labels = me.getLabels();
+ // If we are viewing some subset of labels, slice the original array
+ me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
+ },
+
+ getLabelForIndex: function(index, datasetIndex) {
+ var me = this;
+ var data = me.chart.data;
+ var isHorizontal = me.isHorizontal();
+
+ if (data.yLabels && !isHorizontal) {
+ return me.getRightValue(data.datasets[datasetIndex].data[index]);
+ }
+ return me.ticks[index - me.minIndex];
+ },
+
+ // Used to get data value locations. Value can either be an index or a numerical value
+ getPixelForValue: function(value, index) {
+ var me = this;
+ var offset = me.options.offset;
+ // 1 is added because we need the length but we have the indexes
+ var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
+
+ // If value is a data object, then index is the index in the data array,
+ // not the index of the scale. We need to change that.
+ var valueCategory;
+ if (value !== undefined && value !== null) {
+ valueCategory = me.isHorizontal() ? value.x : value.y;
+ }
+ if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
+ var labels = me.getLabels();
+ value = valueCategory || value;
+ var idx = labels.indexOf(value);
+ index = idx !== -1 ? idx : index;
+ }
+
+ if (me.isHorizontal()) {
+ var valueWidth = me.width / offsetAmt;
+ var widthOffset = (valueWidth * (index - me.minIndex));
+
+ if (offset) {
+ widthOffset += (valueWidth / 2);
+ }
+
+ return me.left + Math.round(widthOffset);
+ }
+ var valueHeight = me.height / offsetAmt;
+ var heightOffset = (valueHeight * (index - me.minIndex));
+
+ if (offset) {
+ heightOffset += (valueHeight / 2);
+ }
+
+ return me.top + Math.round(heightOffset);
+ },
+ getPixelForTick: function(index) {
+ return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
+ },
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var offset = me.options.offset;
+ var value;
+ var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
+ var horz = me.isHorizontal();
+ var valueDimension = (horz ? me.width : me.height) / offsetAmt;
+
+ pixel -= horz ? me.left : me.top;
+
+ if (offset) {
+ pixel -= (valueDimension / 2);
+ }
+
+ if (pixel <= 0) {
+ value = 0;
+ } else {
+ value = Math.round(pixel / valueDimension);
+ }
+
+ return value + me.minIndex;
+ },
+ getBasePixel: function() {
+ return this.bottom;
+ }
+ });
+
+ Chart.scaleService.registerScaleType('category', DatasetScale, defaultConfig);
+
+};
+
+},{}],53:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var helpers = require(45);
+var Ticks = require(34);
+
+module.exports = function(Chart) {
+
+ var defaultConfig = {
+ position: 'left',
+ ticks: {
+ callback: Ticks.formatters.linear
+ }
+ };
+
+ var LinearScale = Chart.LinearScaleBase.extend({
+
+ determineDataLimits: function() {
+ var me = this;
+ var opts = me.options;
+ var chart = me.chart;
+ var data = chart.data;
+ var datasets = data.datasets;
+ var isHorizontal = me.isHorizontal();
+ var DEFAULT_MIN = 0;
+ var DEFAULT_MAX = 1;
+
+ function IDMatches(meta) {
+ return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
+ }
+
+ // First Calculate the range
+ me.min = null;
+ me.max = null;
+
+ var hasStacks = opts.stacked;
+ if (hasStacks === undefined) {
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ if (hasStacks) {
+ return;
+ }
+
+ var meta = chart.getDatasetMeta(datasetIndex);
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
+ meta.stack !== undefined) {
+ hasStacks = true;
+ }
+ });
+ }
+
+ if (opts.stacked || hasStacks) {
+ var valuesPerStack = {};
+
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ var key = [
+ meta.type,
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
+ ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
+ meta.stack
+ ].join('.');
+
+ if (valuesPerStack[key] === undefined) {
+ valuesPerStack[key] = {
+ positiveValues: [],
+ negativeValues: []
+ };
+ }
+
+ // Store these per type
+ var positiveValues = valuesPerStack[key].positiveValues;
+ var negativeValues = valuesPerStack[key].negativeValues;
+
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
+ helpers.each(dataset.data, function(rawValue, index) {
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ positiveValues[index] = positiveValues[index] || 0;
+ negativeValues[index] = negativeValues[index] || 0;
+
+ if (opts.relativePoints) {
+ positiveValues[index] = 100;
+ } else if (value < 0) {
+ negativeValues[index] += value;
+ } else {
+ positiveValues[index] += value;
+ }
+ });
+ }
+ });
+
+ helpers.each(valuesPerStack, function(valuesForType) {
+ var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
+ var minVal = helpers.min(values);
+ var maxVal = helpers.max(values);
+ me.min = me.min === null ? minVal : Math.min(me.min, minVal);
+ me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
+ });
+
+ } else {
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
+ helpers.each(dataset.data, function(rawValue, index) {
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ if (me.min === null) {
+ me.min = value;
+ } else if (value < me.min) {
+ me.min = value;
+ }
+
+ if (me.max === null) {
+ me.max = value;
+ } else if (value > me.max) {
+ me.max = value;
+ }
+ });
+ }
+ });
+ }
+
+ me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
+ me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
+
+ // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
+ this.handleTickRangeOptions();
+ },
+ getTickLimit: function() {
+ var maxTicks;
+ var me = this;
+ var tickOpts = me.options.ticks;
+
+ if (me.isHorizontal()) {
+ maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50));
+ } else {
+ // The factor of 2 used to scale the font size has been experimentally determined.
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize);
+ maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize)));
+ }
+
+ return maxTicks;
+ },
+ // Called after the ticks are built. We need
+ handleDirectionalChanges: function() {
+ if (!this.isHorizontal()) {
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
+ this.ticks.reverse();
+ }
+ },
+ getLabelForIndex: function(index, datasetIndex) {
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
+ },
+ // Utils
+ getPixelForValue: function(value) {
+ // This must be called after fit has been run so that
+ // this.left, this.top, this.right, and this.bottom have been defined
+ var me = this;
+ var start = me.start;
+
+ var rightValue = +me.getRightValue(value);
+ var pixel;
+ var range = me.end - start;
+
+ if (me.isHorizontal()) {
+ pixel = me.left + (me.width / range * (rightValue - start));
+ return Math.round(pixel);
+ }
+
+ pixel = me.bottom - (me.height / range * (rightValue - start));
+ return Math.round(pixel);
+ },
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var isHorizontal = me.isHorizontal();
+ var innerDimension = isHorizontal ? me.width : me.height;
+ var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
+ return me.start + ((me.end - me.start) * offset);
+ },
+ getPixelForTick: function(index) {
+ return this.getPixelForValue(this.ticksAsNumbers[index]);
+ }
+ });
+ Chart.scaleService.registerScaleType('linear', LinearScale, defaultConfig);
+
+};
+
+},{"25":25,"34":34,"45":45}],54:[function(require,module,exports){
+'use strict';
+
+var helpers = require(45);
+var Ticks = require(34);
+
+module.exports = function(Chart) {
+
+ var noop = helpers.noop;
+
+ Chart.LinearScaleBase = Chart.Scale.extend({
+ getRightValue: function(value) {
+ if (typeof value === 'string') {
+ return +value;
+ }
+ return Chart.Scale.prototype.getRightValue.call(this, value);
+ },
+
+ handleTickRangeOptions: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+
+ // If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
+ // do nothing since that would make the chart weird. If the user really wants a weird chart
+ // axis, they can manually override it
+ if (tickOpts.beginAtZero) {
+ var minSign = helpers.sign(me.min);
+ var maxSign = helpers.sign(me.max);
+
+ if (minSign < 0 && maxSign < 0) {
+ // move the top up to 0
+ me.max = 0;
+ } else if (minSign > 0 && maxSign > 0) {
+ // move the bottom down to 0
+ me.min = 0;
+ }
+ }
+
+ var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
+ var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
+
+ if (tickOpts.min !== undefined) {
+ me.min = tickOpts.min;
+ } else if (tickOpts.suggestedMin !== undefined) {
+ if (me.min === null) {
+ me.min = tickOpts.suggestedMin;
+ } else {
+ me.min = Math.min(me.min, tickOpts.suggestedMin);
+ }
+ }
+
+ if (tickOpts.max !== undefined) {
+ me.max = tickOpts.max;
+ } else if (tickOpts.suggestedMax !== undefined) {
+ if (me.max === null) {
+ me.max = tickOpts.suggestedMax;
+ } else {
+ me.max = Math.max(me.max, tickOpts.suggestedMax);
+ }
+ }
+
+ if (setMin !== setMax) {
+ // We set the min or the max but not both.
+ // So ensure that our range is good
+ // Inverted or 0 length range can happen when
+ // ticks.min is set, and no datasets are visible
+ if (me.min >= me.max) {
+ if (setMin) {
+ me.max = me.min + 1;
+ } else {
+ me.min = me.max - 1;
+ }
+ }
+ }
+
+ if (me.min === me.max) {
+ me.max++;
+
+ if (!tickOpts.beginAtZero) {
+ me.min--;
+ }
+ }
+ },
+ getTickLimit: noop,
+ handleDirectionalChanges: noop,
+
+ buildTicks: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+
+ // Figure out what the max number of ticks we can support it is based on the size of
+ // the axis area. For now, we say that the minimum tick spacing in pixels must be 50
+ // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
+ // the graph. Make sure we always have at least 2 ticks
+ var maxTicks = me.getTickLimit();
+ maxTicks = Math.max(2, maxTicks);
+
+ var numericGeneratorOptions = {
+ maxTicks: maxTicks,
+ min: tickOpts.min,
+ max: tickOpts.max,
+ stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
+ };
+ var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me);
+
+ me.handleDirectionalChanges();
+
+ // At this point, we need to update our max and min given the tick values since we have expanded the
+ // range of the scale
+ me.max = helpers.max(ticks);
+ me.min = helpers.min(ticks);
+
+ if (tickOpts.reverse) {
+ ticks.reverse();
+
+ me.start = me.max;
+ me.end = me.min;
+ } else {
+ me.start = me.min;
+ me.end = me.max;
+ }
+ },
+ convertTicksToLabels: function() {
+ var me = this;
+ me.ticksAsNumbers = me.ticks.slice();
+ me.zeroLineIndex = me.ticks.indexOf(0);
+
+ Chart.Scale.prototype.convertTicksToLabels.call(me);
+ }
+ });
+};
+
+},{"34":34,"45":45}],55:[function(require,module,exports){
+'use strict';
+
+var helpers = require(45);
+var Ticks = require(34);
+
+module.exports = function(Chart) {
+
+ var defaultConfig = {
+ position: 'left',
+
+ // label settings
+ ticks: {
+ callback: Ticks.formatters.logarithmic
+ }
+ };
+
+ var LogarithmicScale = Chart.Scale.extend({
+ determineDataLimits: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+ var chart = me.chart;
+ var data = chart.data;
+ var datasets = data.datasets;
+ var valueOrDefault = helpers.valueOrDefault;
+ var isHorizontal = me.isHorizontal();
+ function IDMatches(meta) {
+ return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
+ }
+
+ // Calculate Range
+ me.min = null;
+ me.max = null;
+ me.minNotZero = null;
+
+ var hasStacks = opts.stacked;
+ if (hasStacks === undefined) {
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ if (hasStacks) {
+ return;
+ }
+
+ var meta = chart.getDatasetMeta(datasetIndex);
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
+ meta.stack !== undefined) {
+ hasStacks = true;
+ }
+ });
+ }
+
+ if (opts.stacked || hasStacks) {
+ var valuesPerStack = {};
+
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ var key = [
+ meta.type,
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
+ ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
+ meta.stack
+ ].join('.');
+
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
+ if (valuesPerStack[key] === undefined) {
+ valuesPerStack[key] = [];
+ }
+
+ helpers.each(dataset.data, function(rawValue, index) {
+ var values = valuesPerStack[key];
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ values[index] = values[index] || 0;
+
+ if (opts.relativePoints) {
+ values[index] = 100;
+ } else {
+ // Don't need to split positive and negative since the log scale can't handle a 0 crossing
+ values[index] += value;
+ }
+ });
+ }
+ });
+
+ helpers.each(valuesPerStack, function(valuesForType) {
+ var minVal = helpers.min(valuesForType);
+ var maxVal = helpers.max(valuesForType);
+ me.min = me.min === null ? minVal : Math.min(me.min, minVal);
+ me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
+ });
+
+ } else {
+ helpers.each(datasets, function(dataset, datasetIndex) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+ if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
+ helpers.each(dataset.data, function(rawValue, index) {
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ if (me.min === null) {
+ me.min = value;
+ } else if (value < me.min) {
+ me.min = value;
+ }
+
+ if (me.max === null) {
+ me.max = value;
+ } else if (value > me.max) {
+ me.max = value;
+ }
+
+ if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
+ me.minNotZero = value;
+ }
+ });
+ }
+ });
+ }
+
+ me.min = valueOrDefault(tickOpts.min, me.min);
+ me.max = valueOrDefault(tickOpts.max, me.max);
+
+ if (me.min === me.max) {
+ if (me.min !== 0 && me.min !== null) {
+ me.min = Math.pow(10, Math.floor(helpers.log10(me.min)) - 1);
+ me.max = Math.pow(10, Math.floor(helpers.log10(me.max)) + 1);
+ } else {
+ me.min = 1;
+ me.max = 10;
+ }
+ }
+ },
+ buildTicks: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+
+ var generationOptions = {
+ min: tickOpts.min,
+ max: tickOpts.max
+ };
+ var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me);
+
+ if (!me.isHorizontal()) {
+ // We are in a vertical orientation. The top value is the highest. So reverse the array
+ ticks.reverse();
+ }
+
+ // At this point, we need to update our max and min given the tick values since we have expanded the
+ // range of the scale
+ me.max = helpers.max(ticks);
+ me.min = helpers.min(ticks);
+
+ if (tickOpts.reverse) {
+ ticks.reverse();
+
+ me.start = me.max;
+ me.end = me.min;
+ } else {
+ me.start = me.min;
+ me.end = me.max;
+ }
+ },
+ convertTicksToLabels: function() {
+ this.tickValues = this.ticks.slice();
+
+ Chart.Scale.prototype.convertTicksToLabels.call(this);
+ },
+ // Get the correct tooltip label
+ getLabelForIndex: function(index, datasetIndex) {
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
+ },
+ getPixelForTick: function(index) {
+ return this.getPixelForValue(this.tickValues[index]);
+ },
+ getPixelForValue: function(value) {
+ var me = this;
+ var start = me.start;
+ var newVal = +me.getRightValue(value);
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+ var innerDimension, pixel, range;
+
+ if (me.isHorizontal()) {
+ range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
+ if (newVal === 0) {
+ pixel = me.left;
+ } else {
+ innerDimension = me.width;
+ pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
+ }
+ } else {
+ // Bottom - top since pixels increase downward on a screen
+ innerDimension = me.height;
+ if (start === 0 && !tickOpts.reverse) {
+ range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
+ if (newVal === start) {
+ pixel = me.bottom;
+ } else if (newVal === me.minNotZero) {
+ pixel = me.bottom - innerDimension * 0.02;
+ } else {
+ pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
+ }
+ } else if (me.end === 0 && tickOpts.reverse) {
+ range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
+ if (newVal === me.end) {
+ pixel = me.top;
+ } else if (newVal === me.minNotZero) {
+ pixel = me.top + innerDimension * 0.02;
+ } else {
+ pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
+ }
+ } else if (newVal === 0) {
+ pixel = tickOpts.reverse ? me.top : me.bottom;
+ } else {
+ range = helpers.log10(me.end) - helpers.log10(start);
+ innerDimension = me.height;
+ pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
+ }
+ }
+ return pixel;
+ },
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var range = helpers.log10(me.end) - helpers.log10(me.start);
+ var value, innerDimension;
+
+ if (me.isHorizontal()) {
+ innerDimension = me.width;
+ value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
+ } else { // todo: if start === 0
+ innerDimension = me.height;
+ value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
+ }
+ return value;
+ }
+ });
+ Chart.scaleService.registerScaleType('logarithmic', LogarithmicScale, defaultConfig);
+
+};
+
+},{"34":34,"45":45}],56:[function(require,module,exports){
+'use strict';
+
+var defaults = require(25);
+var helpers = require(45);
+var Ticks = require(34);
+
+module.exports = function(Chart) {
+
+ var globalDefaults = defaults.global;
+
+ var defaultConfig = {
+ display: true,
+
+ // Boolean - Whether to animate scaling the chart from the centre
+ animate: true,
+ position: 'chartArea',
+
+ angleLines: {
+ display: true,
+ color: 'rgba(0, 0, 0, 0.1)',
+ lineWidth: 1
+ },
+
+ gridLines: {
+ circular: false
+ },
+
+ // label settings
+ ticks: {
+ // Boolean - Show a backdrop to the scale label
+ showLabelBackdrop: true,
+
+ // String - The colour of the label backdrop
+ backdropColor: 'rgba(255,255,255,0.75)',
+
+ // Number - The backdrop padding above & below the label in pixels
+ backdropPaddingY: 2,
+
+ // Number - The backdrop padding to the side of the label in pixels
+ backdropPaddingX: 2,
+
+ callback: Ticks.formatters.linear
+ },
+
+ pointLabels: {
+ // Boolean - if true, show point labels
+ display: true,
+
+ // Number - Point label font size in pixels
+ fontSize: 10,
+
+ // Function - Used to convert point labels
+ callback: function(label) {
+ return label;
+ }
+ }
+ };
+
+ function getValueCount(scale) {
+ var opts = scale.options;
+ return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
+ }
+
+ function getPointLabelFontOptions(scale) {
+ var pointLabelOptions = scale.options.pointLabels;
+ var fontSize = helpers.valueOrDefault(pointLabelOptions.fontSize, globalDefaults.defaultFontSize);
+ var fontStyle = helpers.valueOrDefault(pointLabelOptions.fontStyle, globalDefaults.defaultFontStyle);
+ var fontFamily = helpers.valueOrDefault(pointLabelOptions.fontFamily, globalDefaults.defaultFontFamily);
+ var font = helpers.fontString(fontSize, fontStyle, fontFamily);
+
+ return {
+ size: fontSize,
+ style: fontStyle,
+ family: fontFamily,
+ font: font
+ };
+ }
+
+ function measureLabelSize(ctx, fontSize, label) {
+ if (helpers.isArray(label)) {
+ return {
+ w: helpers.longestText(ctx, ctx.font, label),
+ h: (label.length * fontSize) + ((label.length - 1) * 1.5 * fontSize)
+ };
+ }
+
+ return {
+ w: ctx.measureText(label).width,
+ h: fontSize
+ };
+ }
+
+ function determineLimits(angle, pos, size, min, max) {
+ if (angle === min || angle === max) {
+ return {
+ start: pos - (size / 2),
+ end: pos + (size / 2)
+ };
+ } else if (angle < min || angle > max) {
+ return {
+ start: pos - size - 5,
+ end: pos
+ };
+ }
+
+ return {
+ start: pos,
+ end: pos + size + 5
+ };
+ }
+
+ /**
+ * Helper function to fit a radial linear scale with point labels
+ */
+ function fitWithPointLabels(scale) {
+ /*
+ * Right, this is really confusing and there is a lot of maths going on here
+ * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
+ *
+ * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
+ *
+ * Solution:
+ *
+ * We assume the radius of the polygon is half the size of the canvas at first
+ * at each index we check if the text overlaps.
+ *
+ * Where it does, we store that angle and that index.
+ *
+ * After finding the largest index and angle we calculate how much we need to remove
+ * from the shape radius to move the point inwards by that x.
+ *
+ * We average the left and right distances to get the maximum shape radius that can fit in the box
+ * along with labels.
+ *
+ * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
+ * on each side, removing that from the size, halving it and adding the left x protrusion width.
+ *
+ * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
+ * and position it in the most space efficient manner
+ *
+ * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
+ */
+
+ var plFont = getPointLabelFontOptions(scale);
+
+ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
+ // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
+ var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
+ var furthestLimits = {
+ r: scale.width,
+ l: 0,
+ t: scale.height,
+ b: 0
+ };
+ var furthestAngles = {};
+ var i, textSize, pointPosition;
+
+ scale.ctx.font = plFont.font;
+ scale._pointLabelSizes = [];
+
+ var valueCount = getValueCount(scale);
+ for (i = 0; i < valueCount; i++) {
+ pointPosition = scale.getPointPosition(i, largestPossibleRadius);
+ textSize = measureLabelSize(scale.ctx, plFont.size, scale.pointLabels[i] || '');
+ scale._pointLabelSizes[i] = textSize;
+
+ // Add quarter circle to make degree 0 mean top of circle
+ var angleRadians = scale.getIndexAngle(i);
+ var angle = helpers.toDegrees(angleRadians) % 360;
+ var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
+ var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
+
+ if (hLimits.start < furthestLimits.l) {
+ furthestLimits.l = hLimits.start;
+ furthestAngles.l = angleRadians;
+ }
+
+ if (hLimits.end > furthestLimits.r) {
+ furthestLimits.r = hLimits.end;
+ furthestAngles.r = angleRadians;
+ }
+
+ if (vLimits.start < furthestLimits.t) {
+ furthestLimits.t = vLimits.start;
+ furthestAngles.t = angleRadians;
+ }
+
+ if (vLimits.end > furthestLimits.b) {
+ furthestLimits.b = vLimits.end;
+ furthestAngles.b = angleRadians;
+ }
+ }
+
+ scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles);
+ }
+
+ /**
+ * Helper function to fit a radial linear scale with no point labels
+ */
+ function fit(scale) {
+ var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2);
+ scale.drawingArea = Math.round(largestPossibleRadius);
+ scale.setCenterPoint(0, 0, 0, 0);
+ }
+
+ function getTextAlignForAngle(angle) {
+ if (angle === 0 || angle === 180) {
+ return 'center';
+ } else if (angle < 180) {
+ return 'left';
+ }
+
+ return 'right';
+ }
+
+ function fillText(ctx, text, position, fontSize) {
+ if (helpers.isArray(text)) {
+ var y = position.y;
+ var spacing = 1.5 * fontSize;
+
+ for (var i = 0; i < text.length; ++i) {
+ ctx.fillText(text[i], position.x, y);
+ y += spacing;
+ }
+ } else {
+ ctx.fillText(text, position.x, position.y);
+ }
+ }
+
+ function adjustPointPositionForLabelHeight(angle, textSize, position) {
+ if (angle === 90 || angle === 270) {
+ position.y -= (textSize.h / 2);
+ } else if (angle > 270 || angle < 90) {
+ position.y -= textSize.h;
+ }
+ }
+
+ function drawPointLabels(scale) {
+ var ctx = scale.ctx;
+ var valueOrDefault = helpers.valueOrDefault;
+ var opts = scale.options;
+ var angleLineOpts = opts.angleLines;
+ var pointLabelOpts = opts.pointLabels;
+
+ ctx.lineWidth = angleLineOpts.lineWidth;
+ ctx.strokeStyle = angleLineOpts.color;
+
+ var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
+
+ // Point Label Font
+ var plFont = getPointLabelFontOptions(scale);
+
+ ctx.textBaseline = 'top';
+
+ for (var i = getValueCount(scale) - 1; i >= 0; i--) {
+ if (angleLineOpts.display) {
+ var outerPosition = scale.getPointPosition(i, outerDistance);
+ ctx.beginPath();
+ ctx.moveTo(scale.xCenter, scale.yCenter);
+ ctx.lineTo(outerPosition.x, outerPosition.y);
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+ if (pointLabelOpts.display) {
+ // Extra 3px out for some label spacing
+ var pointLabelPosition = scale.getPointPosition(i, outerDistance + 5);
+
+ // Keep this in loop since we may support array properties here
+ var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
+ ctx.font = plFont.font;
+ ctx.fillStyle = pointLabelFontColor;
+
+ var angleRadians = scale.getIndexAngle(i);
+ var angle = helpers.toDegrees(angleRadians);
+ ctx.textAlign = getTextAlignForAngle(angle);
+ adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
+ fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size);
+ }
+ }
+ }
+
+ function drawRadiusLine(scale, gridLineOpts, radius, index) {
+ var ctx = scale.ctx;
+ ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1);
+ ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
+
+ if (scale.options.gridLines.circular) {
+ // Draw circular arcs between the points
+ ctx.beginPath();
+ ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
+ ctx.closePath();
+ ctx.stroke();
+ } else {
+ // Draw straight lines connecting each index
+ var valueCount = getValueCount(scale);
+
+ if (valueCount === 0) {
+ return;
+ }
+
+ ctx.beginPath();
+ var pointPosition = scale.getPointPosition(0, radius);
+ ctx.moveTo(pointPosition.x, pointPosition.y);
+
+ for (var i = 1; i < valueCount; i++) {
+ pointPosition = scale.getPointPosition(i, radius);
+ ctx.lineTo(pointPosition.x, pointPosition.y);
+ }
+
+ ctx.closePath();
+ ctx.stroke();
+ }
+ }
+
+ function numberOrZero(param) {
+ return helpers.isNumber(param) ? param : 0;
+ }
+
+ var LinearRadialScale = Chart.LinearScaleBase.extend({
+ setDimensions: function() {
+ var me = this;
+ var opts = me.options;
+ var tickOpts = opts.ticks;
+ // Set the unconstrained dimension before label rotation
+ me.width = me.maxWidth;
+ me.height = me.maxHeight;
+ me.xCenter = Math.round(me.width / 2);
+ me.yCenter = Math.round(me.height / 2);
+
+ var minSize = helpers.min([me.height, me.width]);
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
+ me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
+ },
+ determineDataLimits: function() {
+ var me = this;
+ var chart = me.chart;
+ var min = Number.POSITIVE_INFINITY;
+ var max = Number.NEGATIVE_INFINITY;
+
+ helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
+ if (chart.isDatasetVisible(datasetIndex)) {
+ var meta = chart.getDatasetMeta(datasetIndex);
+
+ helpers.each(dataset.data, function(rawValue, index) {
+ var value = +me.getRightValue(rawValue);
+ if (isNaN(value) || meta.data[index].hidden) {
+ return;
+ }
+
+ min = Math.min(value, min);
+ max = Math.max(value, max);
+ });
+ }
+ });
+
+ me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
+ me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
+
+ // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
+ me.handleTickRangeOptions();
+ },
+ getTickLimit: function() {
+ var tickOpts = this.options.ticks;
+ var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
+ return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
+ },
+ convertTicksToLabels: function() {
+ var me = this;
+
+ Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
+
+ // Point labels
+ me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
+ },
+ getLabelForIndex: function(index, datasetIndex) {
+ return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
+ },
+ fit: function() {
+ if (this.options.pointLabels.display) {
+ fitWithPointLabels(this);
+ } else {
+ fit(this);
+ }
+ },
+ /**
+ * Set radius reductions and determine new radius and center point
+ * @private
+ */
+ setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
+ var me = this;
+ var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
+ var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
+ var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
+ var radiusReductionBottom = -Math.max(furthestLimits.b - me.height, 0) / Math.cos(furthestAngles.b);
+
+ radiusReductionLeft = numberOrZero(radiusReductionLeft);
+ radiusReductionRight = numberOrZero(radiusReductionRight);
+ radiusReductionTop = numberOrZero(radiusReductionTop);
+ radiusReductionBottom = numberOrZero(radiusReductionBottom);
+
+ me.drawingArea = Math.min(
+ Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
+ Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
+ me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
+ },
+ setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
+ var me = this;
+ var maxRight = me.width - rightMovement - me.drawingArea;
+ var maxLeft = leftMovement + me.drawingArea;
+ var maxTop = topMovement + me.drawingArea;
+ var maxBottom = me.height - bottomMovement - me.drawingArea;
+
+ me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
+ me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top);
+ },
+
+ getIndexAngle: function(index) {
+ var angleMultiplier = (Math.PI * 2) / getValueCount(this);
+ var startAngle = this.chart.options && this.chart.options.startAngle ?
+ this.chart.options.startAngle :
+ 0;
+
+ var startAngleRadians = startAngle * Math.PI * 2 / 360;
+
+ // Start from the top instead of right, so remove a quarter of the circle
+ return index * angleMultiplier + startAngleRadians;
+ },
+ getDistanceFromCenterForValue: function(value) {
+ var me = this;
+
+ if (value === null) {
+ return 0; // null always in center
+ }
+
+ // Take into account half font size + the yPadding of the top value
+ var scalingFactor = me.drawingArea / (me.max - me.min);
+ if (me.options.ticks.reverse) {
+ return (me.max - value) * scalingFactor;
+ }
+ return (value - me.min) * scalingFactor;
+ },
+ getPointPosition: function(index, distanceFromCenter) {
+ var me = this;
+ var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
+ return {
+ x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
+ y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
+ };
+ },
+ getPointPositionForValue: function(index, value) {
+ return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
+ },
+
+ getBasePosition: function() {
+ var me = this;
+ var min = me.min;
+ var max = me.max;
+
+ return me.getPointPositionForValue(0,
+ me.beginAtZero ? 0 :
+ min < 0 && max < 0 ? max :
+ min > 0 && max > 0 ? min :
+ 0);
+ },
+
+ draw: function() {
+ var me = this;
+ var opts = me.options;
+ var gridLineOpts = opts.gridLines;
+ var tickOpts = opts.ticks;
+ var valueOrDefault = helpers.valueOrDefault;
+
+ if (opts.display) {
+ var ctx = me.ctx;
+ var startAngle = this.getIndexAngle(0);
+
+ // Tick Font
+ var tickFontSize = valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
+ var tickFontStyle = valueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
+ var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
+ var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
+
+ helpers.each(me.ticks, function(label, index) {
+ // Don't draw a centre value (if it is minimum)
+ if (index > 0 || tickOpts.reverse) {
+ var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
+
+ // Draw circular lines around the scale
+ if (gridLineOpts.display && index !== 0) {
+ drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
+ }
+
+ if (tickOpts.display) {
+ var tickFontColor = valueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
+ ctx.font = tickLabelFont;
+
+ ctx.save();
+ ctx.translate(me.xCenter, me.yCenter);
+ ctx.rotate(startAngle);
+
+ if (tickOpts.showLabelBackdrop) {
+ var labelWidth = ctx.measureText(label).width;
+ ctx.fillStyle = tickOpts.backdropColor;
+ ctx.fillRect(
+ -labelWidth / 2 - tickOpts.backdropPaddingX,
+ -yCenterOffset - tickFontSize / 2 - tickOpts.backdropPaddingY,
+ labelWidth + tickOpts.backdropPaddingX * 2,
+ tickFontSize + tickOpts.backdropPaddingY * 2
+ );
+ }
+
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillStyle = tickFontColor;
+ ctx.fillText(label, 0, -yCenterOffset);
+ ctx.restore();
+ }
+ }
+ });
+
+ if (opts.angleLines.display || opts.pointLabels.display) {
+ drawPointLabels(me);
+ }
+ }
+ }
+ });
+ Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
+
+};
+
+},{"25":25,"34":34,"45":45}],57:[function(require,module,exports){
+/* global window: false */
+'use strict';
+
+var moment = require(1);
+moment = typeof moment === 'function' ? moment : window.moment;
+
+var defaults = require(25);
+var helpers = require(45);
+
+// Integer constants are from the ES6 spec.
+var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
+var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
+
+var INTERVALS = {
+ millisecond: {
+ common: true,
+ size: 1,
+ steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
+ },
+ second: {
+ common: true,
+ size: 1000,
+ steps: [1, 2, 5, 10, 30]
+ },
+ minute: {
+ common: true,
+ size: 60000,
+ steps: [1, 2, 5, 10, 30]
+ },
+ hour: {
+ common: true,
+ size: 3600000,
+ steps: [1, 2, 3, 6, 12]
+ },
+ day: {
+ common: true,
+ size: 86400000,
+ steps: [1, 2, 5]
+ },
+ week: {
+ common: false,
+ size: 604800000,
+ steps: [1, 2, 3, 4]
+ },
+ month: {
+ common: true,
+ size: 2.628e9,
+ steps: [1, 2, 3]
+ },
+ quarter: {
+ common: false,
+ size: 7.884e9,
+ steps: [1, 2, 3, 4]
+ },
+ year: {
+ common: true,
+ size: 3.154e10
+ }
+};
+
+var UNITS = Object.keys(INTERVALS);
+
+function sorter(a, b) {
+ return a - b;
+}
+
+function arrayUnique(items) {
+ var hash = {};
+ var out = [];
+ var i, ilen, item;
+
+ for (i = 0, ilen = items.length; i < ilen; ++i) {
+ item = items[i];
+ if (!hash[item]) {
+ hash[item] = true;
+ out.push(item);
+ }
+ }
+
+ return out;
+}
+
+/**
+ * Returns an array of {time, pos} objects used to interpolate a specific `time` or position
+ * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
+ * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
+ * extremity (left + width or top + height). Note that it would be more optimized to directly
+ * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
+ * to create the lookup table. The table ALWAYS contains at least two items: min and max.
+ *
+ * @param {Number[]} timestamps - timestamps sorted from lowest to highest.
+ * @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
+ * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
+ * If 'series', timestamps will be positioned at the same distance from each other. In this
+ * case, only timestamps that break the time linearity are registered, meaning that in the
+ * best case, all timestamps are linear, the table contains only min and max.
+ */
+function buildLookupTable(timestamps, min, max, distribution) {
+ if (distribution === 'linear' || !timestamps.length) {
+ return [
+ {time: min, pos: 0},
+ {time: max, pos: 1}
+ ];
+ }
+
+ var table = [];
+ var items = [min];
+ var i, ilen, prev, curr, next;
+
+ for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
+ curr = timestamps[i];
+ if (curr > min && curr < max) {
+ items.push(curr);
+ }
+ }
+
+ items.push(max);
+
+ for (i = 0, ilen = items.length; i < ilen; ++i) {
+ next = items[i + 1];
+ prev = items[i - 1];
+ curr = items[i];
+
+ // only add points that breaks the scale linearity
+ if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
+ table.push({time: curr, pos: i / (ilen - 1)});
+ }
+ }
+
+ return table;
+}
+
+// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
+function lookup(table, key, value) {
+ var lo = 0;
+ var hi = table.length - 1;
+ var mid, i0, i1;
+
+ while (lo >= 0 && lo <= hi) {
+ mid = (lo + hi) >> 1;
+ i0 = table[mid - 1] || null;
+ i1 = table[mid];
+
+ if (!i0) {
+ // given value is outside table (before first item)
+ return {lo: null, hi: i1};
+ } else if (i1[key] < value) {
+ lo = mid + 1;
+ } else if (i0[key] > value) {
+ hi = mid - 1;
+ } else {
+ return {lo: i0, hi: i1};
+ }
+ }
+
+ // given value is outside table (after last item)
+ return {lo: i1, hi: null};
+}
+
+/**
+ * Linearly interpolates the given source `value` using the table items `skey` values and
+ * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
+ * returns the position for a timestamp equal to 42. If value is out of bounds, values at
+ * index [0, 1] or [n - 1, n] are used for the interpolation.
+ */
+function interpolate(table, skey, sval, tkey) {
+ var range = lookup(table, skey, sval);
+
+ // Note: the lookup table ALWAYS contains at least 2 items (min and max)
+ var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
+ var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
+
+ var span = next[skey] - prev[skey];
+ var ratio = span ? (sval - prev[skey]) / span : 0;
+ var offset = (next[tkey] - prev[tkey]) * ratio;
+
+ return prev[tkey] + offset;
+}
+
+/**
+ * Convert the given value to a moment object using the given time options.
+ * @see http://momentjs.com/docs/#/parsing/
+ */
+function momentify(value, options) {
+ var parser = options.parser;
+ var format = options.parser || options.format;
+
+ if (typeof parser === 'function') {
+ return parser(value);
+ }
+
+ if (typeof value === 'string' && typeof format === 'string') {
+ return moment(value, format);
+ }
+
+ if (!(value instanceof moment)) {
+ value = moment(value);
+ }
+
+ if (value.isValid()) {
+ return value;
+ }
+
+ // Labels are in an incompatible moment format and no `parser` has been provided.
+ // The user might still use the deprecated `format` option to convert his inputs.
+ if (typeof format === 'function') {
+ return format(value);
+ }
+
+ return value;
+}
+
+function parse(input, scale) {
+ if (helpers.isNullOrUndef(input)) {
+ return null;
+ }
+
+ var options = scale.options.time;
+ var value = momentify(scale.getRightValue(input), options);
+ if (!value.isValid()) {
+ return null;
+ }
+
+ if (options.round) {
+ value.startOf(options.round);
+ }
+
+ return value.valueOf();
+}
+
+/**
+ * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
+ * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
+ */
+function determineStepSize(min, max, unit, capacity) {
+ var range = max - min;
+ var interval = INTERVALS[unit];
+ var milliseconds = interval.size;
+ var steps = interval.steps;
+ var i, ilen, factor;
+
+ if (!steps) {
+ return Math.ceil(range / ((capacity || 1) * milliseconds));
+ }
+
+ for (i = 0, ilen = steps.length; i < ilen; ++i) {
+ factor = steps[i];
+ if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
+ break;
+ }
+ }
+
+ return factor;
+}
+
+/**
+ * Figures out what unit results in an appropriate number of auto-generated ticks
+ */
+function determineUnitForAutoTicks(minUnit, min, max, capacity) {
+ var ilen = UNITS.length;
+ var i, interval, factor;
+
+ for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
+ interval = INTERVALS[UNITS[i]];
+ factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
+
+ if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
+ return UNITS[i];
+ }
+ }
+
+ return UNITS[ilen - 1];
+}
+
+/**
+ * Figures out what unit to format a set of ticks with
+ */
+function determineUnitForFormatting(ticks, minUnit, min, max) {
+ var duration = moment.duration(moment(max).diff(moment(min)));
+ var ilen = UNITS.length;
+ var i, unit;
+
+ for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
+ unit = UNITS[i];
+ if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) {
+ return unit;
+ }
+ }
+
+ return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
+}
+
+function determineMajorUnit(unit) {
+ for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
+ if (INTERVALS[UNITS[i]].common) {
+ return UNITS[i];
+ }
+ }
+}
+
+/**
+ * Generates a maximum of `capacity` timestamps between min and max, rounded to the
+ * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
+ * Important: this method can return ticks outside the min and max range, it's the
+ * responsibility of the calling code to clamp values if needed.
+ */
+function generate(min, max, capacity, options) {
+ var timeOpts = options.time;
+ var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
+ var major = determineMajorUnit(minor);
+ var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
+ var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
+ var majorTicksEnabled = options.ticks.major.enabled;
+ var interval = INTERVALS[minor];
+ var first = moment(min);
+ var last = moment(max);
+ var ticks = [];
+ var time;
+
+ if (!stepSize) {
+ stepSize = determineStepSize(min, max, minor, capacity);
+ }
+
+ // For 'week' unit, handle the first day of week option
+ if (weekday) {
+ first = first.isoWeekday(weekday);
+ last = last.isoWeekday(weekday);
+ }
+
+ // Align first/last ticks on unit
+ first = first.startOf(weekday ? 'day' : minor);
+ last = last.startOf(weekday ? 'day' : minor);
+
+ // Make sure that the last tick include max
+ if (last < max) {
+ last.add(1, minor);
+ }
+
+ time = moment(first);
+
+ if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
+ // Align the first tick on the previous `minor` unit aligned on the `major` unit:
+ // we first aligned time on the previous `major` unit then add the number of full
+ // stepSize there is between first and the previous major time.
+ time.startOf(major);
+ time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
+ }
+
+ for (; time < last; time.add(stepSize, minor)) {
+ ticks.push(+time);
+ }
+
+ ticks.push(+time);
+
+ return ticks;
+}
+
+/**
+ * Returns the right and left offsets from edges in the form of {left, right}.
+ * Offsets are added when the `offset` option is true.
+ */
+function computeOffsets(table, ticks, min, max, options) {
+ var left = 0;
+ var right = 0;
+ var upper, lower;
+
+ if (options.offset && ticks.length) {
+ if (!options.time.min) {
+ upper = ticks.length > 1 ? ticks[1] : max;
+ lower = ticks[0];
+ left = (
+ interpolate(table, 'time', upper, 'pos') -
+ interpolate(table, 'time', lower, 'pos')
+ ) / 2;
+ }
+ if (!options.time.max) {
+ upper = ticks[ticks.length - 1];
+ lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
+ right = (
+ interpolate(table, 'time', upper, 'pos') -
+ interpolate(table, 'time', lower, 'pos')
+ ) / 2;
+ }
+ }
+
+ return {left: left, right: right};
+}
+
+function ticksFromTimestamps(values, majorUnit) {
+ var ticks = [];
+ var i, ilen, value, major;
+
+ for (i = 0, ilen = values.length; i < ilen; ++i) {
+ value = values[i];
+ major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
+
+ ticks.push({
+ value: value,
+ major: major
+ });
+ }
+
+ return ticks;
+}
+
+module.exports = function(Chart) {
+
+ var defaultConfig = {
+ position: 'bottom',
+
+ /**
+ * Data distribution along the scale:
+ * - 'linear': data are spread according to their time (distances can vary),
+ * - 'series': data are spread at the same distance from each other.
+ * @see https://github.com/chartjs/Chart.js/pull/4507
+ * @since 2.7.0
+ */
+ distribution: 'linear',
+
+ /**
+ * Scale boundary strategy (bypassed by min/max time options)
+ * - `data`: make sure data are fully visible, ticks outside are removed
+ * - `ticks`: make sure ticks are fully visible, data outside are truncated
+ * @see https://github.com/chartjs/Chart.js/pull/4556
+ * @since 2.7.0
+ */
+ bounds: 'data',
+
+ time: {
+ parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
+ format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
+ unit: false, // false == automatic or override with week, month, year, etc.
+ round: false, // none, or override with week, month, year, etc.
+ displayFormat: false, // DEPRECATED
+ isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
+ minUnit: 'millisecond',
+
+ // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
+ displayFormats: {
+ millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
+ second: 'h:mm:ss a', // 11:20:01 AM
+ minute: 'h:mm a', // 11:20 AM
+ hour: 'hA', // 5PM
+ day: 'MMM D', // Sep 4
+ week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
+ month: 'MMM YYYY', // Sept 2015
+ quarter: '[Q]Q - YYYY', // Q3
+ year: 'YYYY' // 2015
+ },
+ },
+ ticks: {
+ autoSkip: false,
+
+ /**
+ * Ticks generation input values:
+ * - 'auto': generates "optimal" ticks based on scale size and time options.
+ * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
+ * - 'labels': generates ticks from user given `data.labels` values ONLY.
+ * @see https://github.com/chartjs/Chart.js/pull/4507
+ * @since 2.7.0
+ */
+ source: 'auto',
+
+ major: {
+ enabled: false
+ }
+ }
+ };
+
+ var TimeScale = Chart.Scale.extend({
+ initialize: function() {
+ if (!moment) {
+ throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
+ }
+
+ this.mergeTicksOptions();
+
+ Chart.Scale.prototype.initialize.call(this);
+ },
+
+ update: function() {
+ var me = this;
+ var options = me.options;
+
+ // DEPRECATIONS: output a message only one time per update
+ if (options.time && options.time.format) {
+ console.warn('options.time.format is deprecated and replaced by options.time.parser.');
+ }
+
+ return Chart.Scale.prototype.update.apply(me, arguments);
+ },
+
+ /**
+ * Allows data to be referenced via 't' attribute
+ */
+ getRightValue: function(rawValue) {
+ if (rawValue && rawValue.t !== undefined) {
+ rawValue = rawValue.t;
+ }
+ return Chart.Scale.prototype.getRightValue.call(this, rawValue);
+ },
+
+ determineDataLimits: function() {
+ var me = this;
+ var chart = me.chart;
+ var timeOpts = me.options.time;
+ var min = MAX_INTEGER;
+ var max = MIN_INTEGER;
+ var timestamps = [];
+ var datasets = [];
+ var labels = [];
+ var i, j, ilen, jlen, data, timestamp;
+
+ // Convert labels to timestamps
+ for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
+ labels.push(parse(chart.data.labels[i], me));
+ }
+
+ // Convert data to timestamps
+ for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
+ if (chart.isDatasetVisible(i)) {
+ data = chart.data.datasets[i].data;
+
+ // Let's consider that all data have the same format.
+ if (helpers.isObject(data[0])) {
+ datasets[i] = [];
+
+ for (j = 0, jlen = data.length; j < jlen; ++j) {
+ timestamp = parse(data[j], me);
+ timestamps.push(timestamp);
+ datasets[i][j] = timestamp;
+ }
+ } else {
+ timestamps.push.apply(timestamps, labels);
+ datasets[i] = labels.slice(0);
+ }
+ } else {
+ datasets[i] = [];
+ }
+ }
+
+ if (labels.length) {
+ // Sort labels **after** data have been converted
+ labels = arrayUnique(labels).sort(sorter);
+ min = Math.min(min, labels[0]);
+ max = Math.max(max, labels[labels.length - 1]);
+ }
+
+ if (timestamps.length) {
+ timestamps = arrayUnique(timestamps).sort(sorter);
+ min = Math.min(min, timestamps[0]);
+ max = Math.max(max, timestamps[timestamps.length - 1]);
+ }
+
+ min = parse(timeOpts.min, me) || min;
+ max = parse(timeOpts.max, me) || max;
+
+ // In case there is no valid min/max, let's use today limits
+ min = min === MAX_INTEGER ? +moment().startOf('day') : min;
+ max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max;
+
+ // Make sure that max is strictly higher than min (required by the lookup table)
+ me.min = Math.min(min, max);
+ me.max = Math.max(min + 1, max);
+
+ // PRIVATE
+ me._horizontal = me.isHorizontal();
+ me._table = [];
+ me._timestamps = {
+ data: timestamps,
+ datasets: datasets,
+ labels: labels
+ };
+ },
+
+ buildTicks: function() {
+ var me = this;
+ var min = me.min;
+ var max = me.max;
+ var options = me.options;
+ var timeOpts = options.time;
+ var timestamps = [];
+ var ticks = [];
+ var i, ilen, timestamp;
+
+ switch (options.ticks.source) {
+ case 'data':
+ timestamps = me._timestamps.data;
+ break;
+ case 'labels':
+ timestamps = me._timestamps.labels;
+ break;
+ case 'auto':
+ default:
+ timestamps = generate(min, max, me.getLabelCapacity(min), options);
+ }
+
+ if (options.bounds === 'ticks' && timestamps.length) {
+ min = timestamps[0];
+ max = timestamps[timestamps.length - 1];
+ }
+
+ // Enforce limits with user min/max options
+ min = parse(timeOpts.min, me) || min;
+ max = parse(timeOpts.max, me) || max;
+
+ // Remove ticks outside the min/max range
+ for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
+ timestamp = timestamps[i];
+ if (timestamp >= min && timestamp <= max) {
+ ticks.push(timestamp);
+ }
+ }
+
+ me.min = min;
+ me.max = max;
+
+ // PRIVATE
+ me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max);
+ me._majorUnit = determineMajorUnit(me._unit);
+ me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
+ me._offsets = computeOffsets(me._table, ticks, min, max, options);
+
+ return ticksFromTimestamps(ticks, me._majorUnit);
+ },
+
+ getLabelForIndex: function(index, datasetIndex) {
+ var me = this;
+ var data = me.chart.data;
+ var timeOpts = me.options.time;
+ var label = data.labels && index < data.labels.length ? data.labels[index] : '';
+ var value = data.datasets[datasetIndex].data[index];
+
+ if (helpers.isObject(value)) {
+ label = me.getRightValue(value);
+ }
+ if (timeOpts.tooltipFormat) {
+ label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
+ }
+
+ return label;
+ },
+
+ /**
+ * Function to format an individual tick mark
+ * @private
+ */
+ tickFormatFunction: function(tick, index, ticks, formatOverride) {
+ var me = this;
+ var options = me.options;
+ var time = tick.valueOf();
+ var formats = options.time.displayFormats;
+ var minorFormat = formats[me._unit];
+ var majorUnit = me._majorUnit;
+ var majorFormat = formats[majorUnit];
+ var majorTime = tick.clone().startOf(majorUnit).valueOf();
+ var majorTickOpts = options.ticks.major;
+ var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
+ var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat);
+ var tickOpts = major ? majorTickOpts : options.ticks.minor;
+ var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
+
+ return formatter ? formatter(label, index, ticks) : label;
+ },
+
+ convertTicksToLabels: function(ticks) {
+ var labels = [];
+ var i, ilen;
+
+ for (i = 0, ilen = ticks.length; i < ilen; ++i) {
+ labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
+ }
+
+ return labels;
+ },
+
+ /**
+ * @private
+ */
+ getPixelForOffset: function(time) {
+ var me = this;
+ var size = me._horizontal ? me.width : me.height;
+ var start = me._horizontal ? me.left : me.top;
+ var pos = interpolate(me._table, 'time', time, 'pos');
+
+ return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
+ },
+
+ getPixelForValue: function(value, index, datasetIndex) {
+ var me = this;
+ var time = null;
+
+ if (index !== undefined && datasetIndex !== undefined) {
+ time = me._timestamps.datasets[datasetIndex][index];
+ }
+
+ if (time === null) {
+ time = parse(value, me);
+ }
+
+ if (time !== null) {
+ return me.getPixelForOffset(time);
+ }
+ },
+
+ getPixelForTick: function(index) {
+ var ticks = this.getTicks();
+ return index >= 0 && index < ticks.length ?
+ this.getPixelForOffset(ticks[index].value) :
+ null;
+ },
+
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var size = me._horizontal ? me.width : me.height;
+ var start = me._horizontal ? me.left : me.top;
+ var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
+ var time = interpolate(me._table, 'pos', pos, 'time');
+
+ return moment(time);
+ },
+
+ /**
+ * Crude approximation of what the label width might be
+ * @private
+ */
+ getLabelWidth: function(label) {
+ var me = this;
+ var ticksOpts = me.options.ticks;
+ var tickLabelWidth = me.ctx.measureText(label).width;
+ var angle = helpers.toRadians(ticksOpts.maxRotation);
+ var cosRotation = Math.cos(angle);
+ var sinRotation = Math.sin(angle);
+ var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
+
+ return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
+ },
+
+ /**
+ * @private
+ */
+ getLabelCapacity: function(exampleTime) {
+ var me = this;
+
+ var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
+
+ var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride);
+ var tickLabelWidth = me.getLabelWidth(exampleLabel);
+ var innerWidth = me.isHorizontal() ? me.width : me.height;
+
+ return Math.floor(innerWidth / tickLabelWidth);
+ }
+ });
+
+ Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
+};
+
+},{"1":1,"25":25,"45":45}]},{},[7])(7)
+});
diff --git a/wp-content/plugins/imagify/assets/js/chart.min.js b/wp-content/plugins/imagify/assets/js/chart.min.js
new file mode 100644
index 00000000..75cc9693
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/chart.min.js
@@ -0,0 +1,10 @@
+/*!
+ * Chart.js
+ * http://chartjs.org/
+ * Version: 2.7.1
+ *
+ * Copyright 2017 Nick Downie
+ * Released under the MIT license
+ * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md
+ */
+window.imagify=window.imagify||{};!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window.imagify:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Chart=t()}}(function(){return function t(e,n,i){function a(r,l){if(!n[r]){if(!e[r]){var s="function"==typeof require&&require;if(!l&&s)return s(r,!0);if(o)return o(r,!0);var u=new Error("Cannot find module '"+r+"'");throw u.code="MODULE_NOT_FOUND",u}var d=n[r]={exports:{}};e[r][0].call(d.exports,function(t){var n=e[r][1][t];return a(n||t)},d,d.exports,t,e,n,i)}return n[r].exports}for(var o="function"==typeof require&&require,r=0;rn?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=this,i=t,a=void 0===e?.5:e,o=2*a-1,r=n.alpha()-i.alpha(),l=((o*r==-1?o:(o+r)/(1+o*r))+1)/2,s=1-l;return this.rgb(l*n.red()+s*i.red(),l*n.green()+s*i.green(),l*n.blue()+s*i.blue()).alpha(n.alpha()*a+i.alpha()*(1-a))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new o,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},o.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},o.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},o.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]}function d(t){var e,n,i,a=u(t),o=a[0],r=a[1],l=a[2];return o/=95.047,r/=100,l/=108.883,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,l=l>.008856?Math.pow(l,1/3):7.787*l+16/116,e=116*r-16,n=500*(o-r),i=200*(r-l),[e,n,i]}function c(t){var e,n,i,a,o,r=t[0]/360,l=t[1]/100,s=t[2]/100;if(0==l)return o=255*s,[o,o,o];e=2*s-(n=s<.5?s*(1+l):s+l-s*l),a=[0,0,0];for(var u=0;u<3;u++)(i=r+1/3*-(u-1))<0&&i++,i>1&&i--,o=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*o;return a}function h(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,o=e-Math.floor(e),r=255*i*(1-n),l=255*i*(1-n*o),s=255*i*(1-n*(1-o)),i=255*i;switch(a){case 0:return[i,s,r];case 1:return[l,i,r];case 2:return[r,i,s];case 3:return[r,l,i];case 4:return[s,r,i];case 5:return[i,r,l]}}function f(t){var e,n,i,a,o=t[0]/360,l=t[1]/100,s=t[2]/100,u=l+s;switch(u>1&&(l/=u,s/=u),e=Math.floor(6*o),n=1-s,i=6*o-e,0!=(1&e)&&(i=1-i),a=l+i*(n-l),e){default:case 6:case 0:r=n,g=a,b=l;break;case 1:r=a,g=n,b=l;break;case 2:r=l,g=n,b=a;break;case 3:r=l,g=a,b=n;break;case 4:r=a,g=l,b=n;break;case 5:r=n,g=l,b=a}return[255*r,255*g,255*b]}function p(t){var e,n,i,a=t[0]/100,o=t[1]/100,r=t[2]/100,l=t[3]/100;return e=1-Math.min(1,a*(1-l)+l),n=1-Math.min(1,o*(1-l)+l),i=1-Math.min(1,r*(1-l)+l),[255*e,255*n,255*i]}function v(t){var e,n,i,a=t[0]/100,o=t[1]/100,r=t[2]/100;return e=3.2406*a+-1.5372*o+-.4986*r,n=-.9689*a+1.8758*o+.0415*r,i=.0557*a+-.204*o+1.057*r,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e*=12.92,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n*=12.92,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i*=12.92,e=Math.min(Math.max(0,e),1),n=Math.min(Math.max(0,n),1),i=Math.min(Math.max(0,i),1),[255*e,255*n,255*i]}function m(t){var e,n,i,a=t[0],o=t[1],r=t[2];return a/=95.047,o/=100,r/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*o-16,n=500*(a-o),i=200*(o-r),[e,n,i]}function x(t){var e,n,i,a,o=t[0],r=t[1],l=t[2];return o<=8?a=(n=100*o/903.3)/100*7.787+16/116:(n=100*Math.pow((o+16)/116,3),a=Math.pow(n/100,1/3)),e=e/95.047<=.008856?e=95.047*(r/500+a-16/116)/7.787:95.047*Math.pow(r/500+a,3),i=i/108.883<=.008859?i=108.883*(a-l/200-16/116)/7.787:108.883*Math.pow(a-l/200,3),[e,n,i]}function y(t){var e,n,i,a=t[0],o=t[1],r=t[2];return e=Math.atan2(r,o),(n=360*e/2/Math.PI)<0&&(n+=360),i=Math.sqrt(o*o+r*r),[a,i,n]}function k(t){return v(x(t))}function w(t){var e,n,i,a=t[0],o=t[1];return i=t[2]/360*2*Math.PI,e=o*Math.cos(i),n=o*Math.sin(i),[a,e,n]}function M(t){return S[t]}e.exports={rgb2hsl:i,rgb2hsv:a,rgb2hwb:o,rgb2cmyk:l,rgb2keyword:s,rgb2xyz:u,rgb2lab:d,rgb2lch:function(t){return y(d(t))},hsl2rgb:c,hsl2hsv:function(t){var e,n,i=t[0],a=t[1]/100,o=t[2]/100;return 0===o?[0,0,0]:(o*=2,a*=o<=1?o:2-o,n=(o+a)/2,e=2*a/(o+a),[i,100*e,100*n])},hsl2hwb:function(t){return o(c(t))},hsl2cmyk:function(t){return l(c(t))},hsl2keyword:function(t){return s(c(t))},hsv2rgb:h,hsv2hsl:function(t){var e,n,i=t[0],a=t[1]/100,o=t[2]/100;return n=(2-a)*o,e=a*o,e/=n<=1?n:2-n,e=e||0,n/=2,[i,100*e,100*n]},hsv2hwb:function(t){return o(h(t))},hsv2cmyk:function(t){return l(h(t))},hsv2keyword:function(t){return s(h(t))},hwb2rgb:f,hwb2hsl:function(t){return i(f(t))},hwb2hsv:function(t){return a(f(t))},hwb2cmyk:function(t){return l(f(t))},hwb2keyword:function(t){return s(f(t))},cmyk2rgb:p,cmyk2hsl:function(t){return i(p(t))},cmyk2hsv:function(t){return a(p(t))},cmyk2hwb:function(t){return o(p(t))},cmyk2keyword:function(t){return s(p(t))},keyword2rgb:M,keyword2hsl:function(t){return i(M(t))},keyword2hsv:function(t){return a(M(t))},keyword2hwb:function(t){return o(M(t))},keyword2cmyk:function(t){return l(M(t))},keyword2lab:function(t){return d(M(t))},keyword2xyz:function(t){return u(M(t))},xyz2rgb:v,xyz2lab:m,xyz2lch:function(t){return y(m(t))},lab2xyz:x,lab2rgb:k,lab2lch:y,lch2lab:w,lch2xyz:function(t){return x(w(t))},lch2rgb:function(t){return k(w(t))}};var S={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},C={};for(var _ in S)C[JSON.stringify(S[_])]=_},{}],5:[function(t,e,n){var i=t(4),a=function(){return new u};for(var o in i){a[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),i[t](e)}}(o);var r=/(\w+)2(\w+)/.exec(o),l=r[1],s=r[2];(a[l]=a[l]||{})[s]=a[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var n=i[t](e);if("string"==typeof n||void 0===n)return n;for(var a=0;a0&&(t[0].yLabel?n=t[0].yLabel:e.labels.length>0&&t[0].index=0&&a>0)&&(v+=a));return o=c.getPixelForValue(v),r=c.getPixelForValue(v+f),l=(r-o)/2,{size:l,base:o,head:r,center:r+l/2}},calculateBarIndexPixels:function(t,e,n){var i,a,r,l,s,u,d=this,c=n.scale.options,h=d.getStackIndex(t),f=n.pixels,g=f[e],p=f.length,v=n.start,m=n.end;return 1===p?(i=g>v?g-v:m-g,a=g0&&(i=(g-f[e-1])/2,e===p-1&&(a=i)),e');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var o=0;o '),a[o]&&e.push(a[o]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(function(n,i){var a=t.getDatasetMeta(0),r=e.datasets[0],l=a.data[i],s=l&&l.custom||{},u=o.valueAtIndexOrDefault,d=t.options.elements.arc;return{text:n,fillStyle:s.backgroundColor?s.backgroundColor:u(r.backgroundColor,i,d.backgroundColor),strokeStyle:s.borderColor?s.borderColor:u(r.borderColor,i,d.borderColor),lineWidth:s.borderWidth?s.borderWidth:u(r.borderWidth,i,d.borderWidth),hidden:isNaN(r.data[i])||a.data[i].hidden,index:i}}):[]}},onClick:function(t,e){var n,i,a,o=e.index,r=this.chart;for(n=0,i=(r.data.datasets||[]).length;n=Math.PI?-1:g<-Math.PI?1:0))+f,v={x:Math.cos(g),y:Math.sin(g)},m={x:Math.cos(p),y:Math.sin(p)},b=g<=0&&p>=0||g<=2*Math.PI&&2*Math.PI<=p,x=g<=.5*Math.PI&&.5*Math.PI<=p||g<=2.5*Math.PI&&2.5*Math.PI<=p,y=g<=-Math.PI&&-Math.PI<=p||g<=Math.PI&&Math.PI<=p,k=g<=.5*-Math.PI&&.5*-Math.PI<=p||g<=1.5*Math.PI&&1.5*Math.PI<=p,w=h/100,M={x:y?-1:Math.min(v.x*(v.x<0?1:w),m.x*(m.x<0?1:w)),y:k?-1:Math.min(v.y*(v.y<0?1:w),m.y*(m.y<0?1:w))},S={x:b?1:Math.max(v.x*(v.x>0?1:w),m.x*(m.x>0?1:w)),y:x?1:Math.max(v.y*(v.y>0?1:w),m.y*(m.y>0?1:w))},C={width:.5*(S.x-M.x),height:.5*(S.y-M.y)};u=Math.min(l/C.width,s/C.height),d={x:-.5*(S.x+M.x),y:-.5*(S.y+M.y)}}n.borderWidth=e.getMaxBorderWidth(c.data),n.outerRadius=Math.max((u-n.borderWidth)/2,0),n.innerRadius=Math.max(h?n.outerRadius/100*h:0,0),n.radiusLength=(n.outerRadius-n.innerRadius)/n.getVisibleDatasetCount(),n.offsetX=d.x*n.outerRadius,n.offsetY=d.y*n.outerRadius,c.total=e.calculateTotal(),e.outerRadius=n.outerRadius-n.radiusLength*e.getRingIndex(e.index),e.innerRadius=Math.max(e.outerRadius-n.radiusLength,0),o.each(c.data,function(n,i){e.updateElement(n,i,t)})},updateElement:function(t,e,n){var i=this,a=i.chart,r=a.chartArea,l=a.options,s=l.animation,u=(r.left+r.right)/2,d=(r.top+r.bottom)/2,c=l.rotation,h=l.rotation,f=i.getDataset(),g=n&&s.animateRotate?0:t.hidden?0:i.calculateCircumference(f.data[e])*(l.circumference/(2*Math.PI)),p=n&&s.animateScale?0:i.innerRadius,v=n&&s.animateScale?0:i.outerRadius,m=o.valueAtIndexOrDefault;o.extend(t,{_datasetIndex:i.index,_index:e,_model:{x:u+a.offsetX,y:d+a.offsetY,startAngle:c,endAngle:h,circumference:g,outerRadius:v,innerRadius:p,label:m(f.label,e,a.data.labels[e])}});var b=t._model;this.removeHoverStyle(t),n&&s.animateRotate||(b.startAngle=0===e?l.rotation:i.getMeta().data[e-1]._model.endAngle,b.endAngle=b.startAngle+b.circumference),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},calculateTotal:function(){var t,e=this.getDataset(),n=this.getMeta(),i=0;return o.each(n.data,function(n,a){t=e.data[a],isNaN(t)||n.hidden||(i+=Math.abs(t))}),i},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(t/e):0},getMaxBorderWidth:function(t){for(var e,n,i=0,a=this.index,o=t.length,r=0;r(i=e>i?e:i)?n:i;return i}})}},{25:25,40:40,45:45}],18:[function(t,e,n){"use strict";var i=t(25),a=t(40),o=t(45);i._set("line",{showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}}),e.exports=function(t){function e(t,e){return o.valueOrDefault(t.showLine,e.showLines)}t.controllers.line=t.DatasetController.extend({datasetElementType:a.Line,dataElementType:a.Point,update:function(t){var n,i,a,r=this,l=r.getMeta(),s=l.dataset,u=l.data||[],d=r.chart.options,c=d.elements.line,h=r.getScaleForId(l.yAxisID),f=r.getDataset(),g=e(f,d);for(g&&(a=s.custom||{},void 0!==f.tension&&void 0===f.lineTension&&(f.lineTension=f.tension),s._scale=h,s._datasetIndex=r.index,s._children=u,s._model={spanGaps:f.spanGaps?f.spanGaps:d.spanGaps,tension:a.tension?a.tension:o.valueOrDefault(f.lineTension,c.tension),backgroundColor:a.backgroundColor?a.backgroundColor:f.backgroundColor||c.backgroundColor,borderWidth:a.borderWidth?a.borderWidth:f.borderWidth||c.borderWidth,borderColor:a.borderColor?a.borderColor:f.borderColor||c.borderColor,borderCapStyle:a.borderCapStyle?a.borderCapStyle:f.borderCapStyle||c.borderCapStyle,borderDash:a.borderDash?a.borderDash:f.borderDash||c.borderDash,borderDashOffset:a.borderDashOffset?a.borderDashOffset:f.borderDashOffset||c.borderDashOffset,borderJoinStyle:a.borderJoinStyle?a.borderJoinStyle:f.borderJoinStyle||c.borderJoinStyle,fill:a.fill?a.fill:void 0!==f.fill?f.fill:c.fill,steppedLine:a.steppedLine?a.steppedLine:o.valueOrDefault(f.steppedLine,c.stepped),cubicInterpolationMode:a.cubicInterpolationMode?a.cubicInterpolationMode:o.valueOrDefault(f.cubicInterpolationMode,c.cubicInterpolationMode)},s.pivot()),n=0,i=u.length;n');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var o=0;o '),a[o]&&e.push(a[o]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(function(n,i){var a=t.getDatasetMeta(0),r=e.datasets[0],l=a.data[i].custom||{},s=o.valueAtIndexOrDefault,u=t.options.elements.arc;return{text:n,fillStyle:l.backgroundColor?l.backgroundColor:s(r.backgroundColor,i,u.backgroundColor),strokeStyle:l.borderColor?l.borderColor:s(r.borderColor,i,u.borderColor),lineWidth:l.borderWidth?l.borderWidth:s(r.borderWidth,i,u.borderWidth),hidden:isNaN(r.data[i])||a.data[i].hidden,index:i}}):[]}},onClick:function(t,e){var n,i,a,o=e.index,r=this.chart;for(n=0,i=(r.data.datasets||[]).length;n0&&!isNaN(t)?2*Math.PI/e:0}})}},{25:25,40:40,45:45}],20:[function(t,e,n){"use strict";var i=t(25),a=t(40),o=t(45);i._set("radar",{scale:{type:"radialLinear"},elements:{line:{tension:0}}}),e.exports=function(t){t.controllers.radar=t.DatasetController.extend({datasetElementType:a.Line,dataElementType:a.Point,linkScales:o.noop,update:function(t){var e=this,n=e.getMeta(),i=n.dataset,a=n.data,r=i.custom||{},l=e.getDataset(),s=e.chart.options.elements.line,u=e.chart.scale;void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),o.extend(n.dataset,{_datasetIndex:e.index,_scale:u,_children:a,_loop:!0,_model:{tension:r.tension?r.tension:o.valueOrDefault(l.lineTension,s.tension),backgroundColor:r.backgroundColor?r.backgroundColor:l.backgroundColor||s.backgroundColor,borderWidth:r.borderWidth?r.borderWidth:l.borderWidth||s.borderWidth,borderColor:r.borderColor?r.borderColor:l.borderColor||s.borderColor,fill:r.fill?r.fill:void 0!==l.fill?l.fill:s.fill,borderCapStyle:r.borderCapStyle?r.borderCapStyle:l.borderCapStyle||s.borderCapStyle,borderDash:r.borderDash?r.borderDash:l.borderDash||s.borderDash,borderDashOffset:r.borderDashOffset?r.borderDashOffset:l.borderDashOffset||s.borderDashOffset,borderJoinStyle:r.borderJoinStyle?r.borderJoinStyle:l.borderJoinStyle||s.borderJoinStyle}}),n.dataset.pivot(),o.each(a,function(n,i){e.updateElement(n,i,t)},e),e.updateBezierControlPoints()},updateElement:function(t,e,n){var i=this,a=t.custom||{},r=i.getDataset(),l=i.chart.scale,s=i.chart.options.elements.point,u=l.getPointPositionForValue(e,r.data[e]);void 0!==r.radius&&void 0===r.pointRadius&&(r.pointRadius=r.radius),void 0!==r.hitRadius&&void 0===r.pointHitRadius&&(r.pointHitRadius=r.hitRadius),o.extend(t,{_datasetIndex:i.index,_index:e,_scale:l,_model:{x:n?l.xCenter:u.x,y:n?l.yCenter:u.y,tension:a.tension?a.tension:o.valueOrDefault(r.lineTension,i.chart.options.elements.line.tension),radius:a.radius?a.radius:o.valueAtIndexOrDefault(r.pointRadius,e,s.radius),backgroundColor:a.backgroundColor?a.backgroundColor:o.valueAtIndexOrDefault(r.pointBackgroundColor,e,s.backgroundColor),borderColor:a.borderColor?a.borderColor:o.valueAtIndexOrDefault(r.pointBorderColor,e,s.borderColor),borderWidth:a.borderWidth?a.borderWidth:o.valueAtIndexOrDefault(r.pointBorderWidth,e,s.borderWidth),pointStyle:a.pointStyle?a.pointStyle:o.valueAtIndexOrDefault(r.pointStyle,e,s.pointStyle),hitRadius:a.hitRadius?a.hitRadius:o.valueAtIndexOrDefault(r.pointHitRadius,e,s.hitRadius)}}),t._model.skip=a.skip?a.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.chart.chartArea,e=this.getMeta();o.each(e.data,function(n,i){var a=n._model,r=o.splineCurve(o.previousItem(e.data,i,!0)._model,a,o.nextItem(e.data,i,!0)._model,a.tension);a.controlPointPreviousX=Math.max(Math.min(r.previous.x,t.right),t.left),a.controlPointPreviousY=Math.max(Math.min(r.previous.y,t.bottom),t.top),a.controlPointNextX=Math.max(Math.min(r.next.x,t.right),t.left),a.controlPointNextY=Math.max(Math.min(r.next.y,t.bottom),t.top),n.pivot()})},setHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],n=t.custom||{},i=t._index,a=t._model;a.radius=n.hoverRadius?n.hoverRadius:o.valueAtIndexOrDefault(e.pointHoverRadius,i,this.chart.options.elements.point.hoverRadius),a.backgroundColor=n.hoverBackgroundColor?n.hoverBackgroundColor:o.valueAtIndexOrDefault(e.pointHoverBackgroundColor,i,o.getHoverColor(a.backgroundColor)),a.borderColor=n.hoverBorderColor?n.hoverBorderColor:o.valueAtIndexOrDefault(e.pointHoverBorderColor,i,o.getHoverColor(a.borderColor)),a.borderWidth=n.hoverBorderWidth?n.hoverBorderWidth:o.valueAtIndexOrDefault(e.pointHoverBorderWidth,i,a.borderWidth)},removeHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],n=t.custom||{},i=t._index,a=t._model,r=this.chart.options.elements.point;a.radius=n.radius?n.radius:o.valueAtIndexOrDefault(e.pointRadius,i,r.radius),a.backgroundColor=n.backgroundColor?n.backgroundColor:o.valueAtIndexOrDefault(e.pointBackgroundColor,i,r.backgroundColor),a.borderColor=n.borderColor?n.borderColor:o.valueAtIndexOrDefault(e.pointBorderColor,i,r.borderColor),a.borderWidth=n.borderWidth?n.borderWidth:o.valueAtIndexOrDefault(e.pointBorderWidth,i,r.borderWidth)}})}},{25:25,40:40,45:45}],21:[function(t,e,n){"use strict";t(25)._set("scatter",{hover:{mode:"single"},scales:{xAxes:[{id:"x-axis-1",type:"linear",position:"bottom"}],yAxes:[{id:"y-axis-1",type:"linear",position:"left"}]},showLines:!1,tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}}),e.exports=function(t){t.controllers.scatter=t.controllers.line}},{25:25}],22:[function(t,e,n){"use strict";var i=t(25),a=t(26),o=t(45);i._set("global",{animation:{duration:1e3,easing:"easeOutQuart",onProgress:o.noop,onComplete:o.noop}}),e.exports=function(t){t.Animation=a.extend({chart:null,currentStep:0,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,n,i){var a,o,r=this.animations;for(e.chart=t,i||(t.animating=!0),a=0,o=r.length;a1&&(n=Math.floor(t.dropFrames),t.dropFrames=t.dropFrames%1),t.advance(1+n);var i=Date.now();t.dropFrames+=(i-e)/t.frameDuration,t.animations.length>0&&t.requestAnimationFrame()},advance:function(t){for(var e,n,i=this.animations,a=0;a=e.numSteps?(o.callback(e.onAnimationComplete,[e],n),n.animating=!1,i.splice(a,1)):++a}},Object.defineProperty(t.Animation.prototype,"animationObject",{get:function(){return this}}),Object.defineProperty(t.Animation.prototype,"chartInstance",{get:function(){return this.chart},set:function(t){this.chart=t}})}},{25:25,26:26,45:45}],23:[function(t,e,n){"use strict";var i=t(25),a=t(45),o=t(28),r=t(48);e.exports=function(t){function e(t){var e=(t=t||{}).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=a.configMerge(i.global,i[t.type],t.options||{}),t}function n(t){var e=t.options;e.scale?t.scale.options=e.scale:e.scales&&e.scales.xAxes.concat(e.scales.yAxes).forEach(function(e){t.scales[e.id].options=e}),t.tooltip._options=e.tooltips}function l(t){return"top"===t||"bottom"===t}var s=t.plugins;t.types={},t.instances={},t.controllers={},a.extend(t.prototype,{construct:function(n,i){var o=this;i=e(i);var l=r.acquireContext(n,i),s=l&&l.canvas,u=s&&s.height,d=s&&s.width;o.id=a.uid(),o.ctx=l,o.canvas=s,o.config=i,o.width=d,o.height=u,o.aspectRatio=u?d/u:null,o.options=i.options,o._bufferedRender=!1,o.chart=o,o.controller=o,t.instances[o.id]=o,Object.defineProperty(o,"data",{get:function(){return o.config.data},set:function(t){o.config.data=t}}),l&&s?(o.initialize(),o.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return s.notify(t,"beforeInit"),a.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.ensureScalesHaveIDs(),t.buildScales(),t.initToolTip(),s.notify(t,"afterInit"),t},clear:function(){return a.canvas.clear(this),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,o=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(a.getMaximumWidth(i))),l=Math.max(0,Math.floor(o?r/o:a.getMaximumHeight(i)));if((e.width!==r||e.height!==l)&&(i.width=e.width=r,i.height=e.height=l,i.style.width=r+"px",i.style.height=l+"px",a.retinaScale(e,n.devicePixelRatio),!t)){var u={width:r,height:l};s.notify(e,"resize",[u]),e.options.onResize&&e.options.onResize(e,u),e.stop(),e.update(e.options.responsiveAnimationDuration)}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;a.each(e.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),a.each(e.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),n&&(n.id=n.id||"scale")},buildScales:function(){var e=this,n=e.options,i=e.scales={},o=[];n.scales&&(o=o.concat((n.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category",dposition:"bottom"}}),(n.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear",dposition:"left"}}))),n.scale&&o.push({options:n.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),a.each(o,function(n){var o=n.options,r=a.valueOrDefault(o.type,n.dtype),s=t.scaleService.getScaleConstructor(r);if(s){l(o.position)!==l(n.dposition)&&(o.position=n.dposition);var u=new s({id:o.id,options:o,ctx:e.ctx,chart:e});i[u.id]=u,u.mergeTicksOptions(),n.isDefault&&(e.scale=u)}}),t.scaleService.addScalesToLayout(this)},buildOrUpdateControllers:function(){var e=this,n=[],i=[];return a.each(e.data.datasets,function(a,o){var r=e.getDatasetMeta(o),l=a.type||e.config.type;if(r.type&&r.type!==l&&(e.destroyDatasetMeta(o),r=e.getDatasetMeta(o)),r.type=l,n.push(r.type),r.controller)r.controller.updateIndex(o);else{var s=t.controllers[r.type];if(void 0===s)throw new Error('"'+r.type+'" is not a chart type.');r.controller=new s(e,o),i.push(r.controller)}},e),i},resetElements:function(){var t=this;a.each(t.data.datasets,function(e,n){t.getDatasetMeta(n).controller.reset()},t)},reset:function(){this.resetElements(),this.tooltip.initialize()},update:function(t){var e=this;if(t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]}),n(e),!1!==s.notify(e,"beforeUpdate")){e.tooltip._data=e.data;var i=e.buildOrUpdateControllers();a.each(e.data.datasets,function(t,n){e.getDatasetMeta(n).controller.buildOrUpdateElements()},e),e.updateLayout(),a.each(i,function(t){t.reset()}),e.updateDatasets(),e.tooltip.initialize(),e.lastActive=[],s.notify(e,"afterUpdate"),e._bufferedRender?e._bufferedRequest={duration:t.duration,easing:t.easing,lazy:t.lazy}:e.render(t)}},updateLayout:function(){var e=this;!1!==s.notify(e,"beforeLayout")&&(t.layoutService.update(this,this.width,this.height),s.notify(e,"afterScaleUpdate"),s.notify(e,"afterLayout"))},updateDatasets:function(){var t=this;if(!1!==s.notify(t,"beforeDatasetsUpdate")){for(var e=0,n=t.data.datasets.length;e=0;--n)e.isDatasetVisible(n)&&e.drawDataset(n,t);s.notify(e,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n=this,i=n.getDatasetMeta(t),a={meta:i,index:t,easingValue:e};!1!==s.notify(n,"beforeDatasetDraw",[a])&&(i.controller.draw(e),s.notify(n,"afterDatasetDraw",[a]))},_drawTooltip:function(t){var e=this,n=e.tooltip,i={tooltip:n,easingValue:t};!1!==s.notify(e,"beforeTooltipDraw",[i])&&(n.draw(),s.notify(e,"afterTooltipDraw",[i]))},getElementAtEvent:function(t){return o.modes.single(this,t)},getElementsAtEvent:function(t){return o.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return o.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=o.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return o.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this,n=e.data.datasets[t];n._meta||(n._meta={});var i=n._meta[e.id];return i||(i=n._meta[e.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null}),i},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e0||(a.forEach(function(e){delete t[e]}),delete t._chartjs)}}var a=["push","pop","shift","splice","unshift"];t.DatasetController=function(t,e){this.initialize(t,e)},i.extend(t.DatasetController.prototype,{datasetElementType:null,dataElementType:null,initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements()},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),n=t.getDataset();null===e.xAxisID&&(e.xAxisID=n.xAxisID||t.chart.options.scales.xAxes[0].id),null===e.yAxisID&&(e.yAxisID=n.yAxisID||t.chart.options.scales.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},reset:function(){this.update(!0)},destroy:function(){this._data&&n(this._data,this)},createMetaDataset:function(){var t=this,e=t.datasetElementType;return e&&new e({_chart:t.chart,_datasetIndex:t.index})},createMetaData:function(t){var e=this,n=e.dataElementType;return n&&new n({_chart:e.chart,_datasetIndex:e.index,_index:t})},addElements:function(){var t,e,n=this,i=n.getMeta(),a=n.getDataset().data||[],o=i.data;for(t=0,e=a.length;ti&&t.insertElements(i,a-i)},insertElements:function(t,e){for(var n=0;n=n[e].length&&n[e].push({}),!n[e][r].type||s.type&&s.type!==n[e][r].type?o.merge(n[e][r],[t.scaleService.getScaleDefaults(l),s]):o.merge(n[e][r],s)}else o._merger(e,n,i,a)}})},o.where=function(t,e){if(o.isArray(t)&&Array.prototype.filter)return t.filter(e);var n=[];return o.each(t,function(t){e(t)&&n.push(t)}),n},o.findIndex=Array.prototype.findIndex?function(t,e,n){return t.findIndex(e,n)}:function(t,e,n){n=void 0===n?t:n;for(var i=0,a=t.length;i=0;i--){var a=t[i];if(e(a))return a}},o.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},o.almostEquals=function(t,e,n){return Math.abs(t-e)t},o.max=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.max(t,e)},Number.NEGATIVE_INFINITY)},o.min=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.min(t,e)},Number.POSITIVE_INFINITY)},o.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0==(t=+t)||isNaN(t)?t:t>0?1:-1},o.log10=Math.log10?function(t){return Math.log10(t)}:function(t){return Math.log(t)/Math.LN10},o.toRadians=function(t){return t*(Math.PI/180)},o.toDegrees=function(t){return t*(180/Math.PI)},o.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),o=Math.atan2(i,n);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:a}},o.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},o.aliasPixel=function(t){return t%2==0?0:.5},o.splineCurve=function(t,e,n,i){var a=t.skip?e:t,o=e,r=n.skip?e:n,l=Math.sqrt(Math.pow(o.x-a.x,2)+Math.pow(o.y-a.y,2)),s=Math.sqrt(Math.pow(r.x-o.x,2)+Math.pow(r.y-o.y,2)),u=l/(l+s),d=s/(l+s),c=i*(u=isNaN(u)?0:u),h=i*(d=isNaN(d)?0:d);return{previous:{x:o.x-c*(r.x-a.x),y:o.y-c*(r.y-a.y)},next:{x:o.x+h*(r.x-a.x),y:o.y+h*(r.y-a.y)}}},o.EPSILON=Number.EPSILON||1e-14,o.splineCurveMonotone=function(t){var e,n,i,a,r=(t||[]).map(function(t){return{model:t._model,deltaK:0,mK:0}}),l=r.length;for(e=0;e0?r[e-1]:null,(a=e0?r[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},o.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},o.niceNum=function(t,e){var n=Math.floor(o.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},o.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},o.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.currentTarget||t.srcElement,l=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var u=parseFloat(o.getStyle(r,"padding-left")),d=parseFloat(o.getStyle(r,"padding-top")),c=parseFloat(o.getStyle(r,"padding-right")),h=parseFloat(o.getStyle(r,"padding-bottom")),f=l.right-l.left-u-c,g=l.bottom-l.top-d-h;return n=Math.round((n-l.left-u)/f*r.width/e.currentDevicePixelRatio),i=Math.round((i-l.top-d)/g*r.height/e.currentDevicePixelRatio),{x:n,y:i}},o.getConstraintWidth=function(t){return r(t,"max-width","clientWidth")},o.getConstraintHeight=function(t){return r(t,"max-height","clientHeight")},o.getMaximumWidth=function(t){var e=t.parentNode;if(!e)return t.clientWidth;var n=parseInt(o.getStyle(e,"padding-left"),10),i=parseInt(o.getStyle(e,"padding-right"),10),a=e.clientWidth-n-i,r=o.getConstraintWidth(t);return isNaN(r)?a:Math.min(a,r)},o.getMaximumHeight=function(t){var e=t.parentNode;if(!e)return t.clientHeight;var n=parseInt(o.getStyle(e,"padding-top"),10),i=parseInt(o.getStyle(e,"padding-bottom"),10),a=e.clientHeight-n-i,r=o.getConstraintHeight(t);return isNaN(r)?a:Math.min(a,r)},o.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},o.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,o=t.width;i.height=a*n,i.width=o*n,t.ctx.scale(n,n),i.style.height=a+"px",i.style.width=o+"px"}},o.fontString=function(t,e,n){return e+" "+t+"px "+n},o.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var l=0;o.each(n,function(e){void 0!==e&&null!==e&&!0!==o.isArray(e)?l=o.measureText(t,a,r,l,e):o.isArray(e)&&o.each(e,function(e){void 0===e||null===e||o.isArray(e)||(l=o.measureText(t,a,r,l,e))})});var s=r.length/2;if(s>n.length){for(var u=0;ui&&(i=o),i},o.numberOfLabelLines=function(t){var e=1;return o.each(t,function(t){o.isArray(t)&&t.length>e&&(e=t.length)}),e},o.color=i?function(t){return t instanceof CanvasGradient&&(t=a.global.defaultColor),i(t)}:function(t){return console.error("Color.js not found!"),t},o.getHoverColor=function(t){return t instanceof CanvasPattern?t:o.color(t).saturate(.5).darken(.1).rgbString()}}},{25:25,3:3,45:45}],28:[function(t,e,n){"use strict";function i(t,e){return t.native?{x:t.x,y:t.y}:u.getRelativePosition(t,e)}function a(t,e){var n,i,a,o,r;for(i=0,o=t.data.datasets.length;i0&&(u=t.getDatasetMeta(u[0]._datasetIndex).data),u},"x-axis":function(t,e){return s(t,e,{intersect:!1})},point:function(t,e){return o(t,i(e,t))},nearest:function(t,e,n){var a=i(e,t);n.axis=n.axis||"xy";var o=l(n.axis),s=r(t,a,n.intersect,o);return s.length>1&&s.sort(function(t,e){var n=t.getArea()-e.getArea();return 0===n&&(n=t._datasetIndex-e._datasetIndex),n}),s.slice(0,1)},x:function(t,e,n){var o=i(e,t),r=[],l=!1;return a(t,function(t){t.inXRange(o.x)&&r.push(t),t.inRange(o.x,o.y)&&(l=!0)}),n.intersect&&!l&&(r=[]),r},y:function(t,e,n){var o=i(e,t),r=[],l=!1;return a(t,function(t){t.inYRange(o.y)&&r.push(t),t.inRange(o.x,o.y)&&(l=!0)}),n.intersect&&!l&&(r=[]),r}}}},{45:45}],29:[function(t,e,n){"use strict";t(25)._set("global",{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},layout:{padding:{top:0,right:0,bottom:0,left:0}}}),e.exports=function(){var t=function(t,e){return this.construct(t,e),this};return t.Chart=t,t}},{25:25}],30:[function(t,e,n){"use strict";var i=t(45);e.exports=function(t){function e(t,e){return i.where(t,function(t){return t.position===e})}function n(t,e){t.forEach(function(t,e){return t._tmpIndex_=e,t}),t.sort(function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i._tmpIndex_-a._tmpIndex_:i.weight-a.weight}),t.forEach(function(t){delete t._tmpIndex_})}t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),e.fullWidth=e.fullWidth||!1,e.position=e.position||"top",e.weight=e.weight||0,t.boxes.push(e)},removeBox:function(t,e){var n=t.boxes?t.boxes.indexOf(e):-1;-1!==n&&t.boxes.splice(n,1)},configure:function(t,e,n){for(var i,a=["fullWidth","position","weight"],o=a.length,r=0;rh&&st.maxHeight){s--;break}s++,c=u*d}t.labelRotation=s},afterCalculateTickRotation:function(){l.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){l.callback(this.options.beforeFit,[this])},fit:function(){var t=this,a=t.minSize={width:0,height:0},o=i(t._ticks),r=t.options,u=r.ticks,d=r.scaleLabel,c=r.gridLines,h=r.display,f=t.isHorizontal(),g=n(u),p=r.gridLines.tickMarkLength;if(a.width=f?t.isFullWidth()?t.maxWidth-t.margins.left-t.margins.right:t.maxWidth:h&&c.drawTicks?p:0,a.height=f?h&&c.drawTicks?p:0:t.maxHeight,d.display&&h){var v=s(d)+l.options.toPadding(d.padding).height;f?a.height+=v:a.width+=v}if(u.display&&h){var m=l.longestText(t.ctx,g.font,o,t.longestTextCache),b=l.numberOfLabelLines(o),x=.5*g.size,y=t.options.ticks.padding;if(f){t.longestLabelWidth=m;var k=l.toRadians(t.labelRotation),w=Math.cos(k),M=Math.sin(k)*m+g.size*b+x*(b-1)+x;a.height=Math.min(t.maxHeight,a.height+M+y),t.ctx.font=g.font;var S=e(t.ctx,o[0],g.font),C=e(t.ctx,o[o.length-1],g.font);0!==t.labelRotation?(t.paddingLeft="bottom"===r.position?w*S+3:w*x+3,t.paddingRight="bottom"===r.position?w*x+3:w*C+3):(t.paddingLeft=S/2+3,t.paddingRight=C/2+3)}else u.mirror?m=0:m+=y+x,a.width=Math.min(t.maxWidth,a.width+m),t.paddingTop=g.size/2,t.paddingBottom=g.size/2}t.handleMargins(),t.width=a.width,t.height=a.height},handleMargins:function(){var t=this;t.margins&&(t.paddingLeft=Math.max(t.paddingLeft-t.margins.left,0),t.paddingTop=Math.max(t.paddingTop-t.margins.top,0),t.paddingRight=Math.max(t.paddingRight-t.margins.right,0),t.paddingBottom=Math.max(t.paddingBottom-t.margins.bottom,0))},afterFit:function(){l.callback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(l.isNullOrUndef(t))return NaN;if("number"==typeof t&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},getLabelForIndex:l.noop,getPixelForValue:l.noop,getValueForPixel:l.noop,getPixelForTick:function(t){var e=this,n=e.options.offset;if(e.isHorizontal()){var i=(e.width-(e.paddingLeft+e.paddingRight))/Math.max(e._ticks.length-(n?0:1),1),a=i*t+e.paddingLeft;n&&(a+=i/2);var o=e.left+Math.round(a);return o+=e.isFullWidth()?e.margins.left:0}var r=e.height-(e.paddingTop+e.paddingBottom);return e.top+t*(r/(e._ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var n=(e.width-(e.paddingLeft+e.paddingRight))*t+e.paddingLeft,i=e.left+Math.round(n);return i+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this,e=t.min,n=t.max;return t.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0},_autoSkip:function(t){var e,n,i,a,o=this,r=o.isHorizontal(),s=o.options.ticks.minor,u=t.length,d=l.toRadians(o.labelRotation),c=Math.cos(d),h=o.longestLabelWidth*c,f=[];for(s.maxTicksLimit&&(a=s.maxTicksLimit),r&&(e=!1,(h+s.autoSkipPadding)*u>o.width-(o.paddingLeft+o.paddingRight)&&(e=1+Math.floor((h+s.autoSkipPadding)*u/(o.width-(o.paddingLeft+o.paddingRight)))),a&&u>a&&(e=Math.max(e,Math.floor(u/a)))),n=0;n1&&n%e>0||n%e==0&&n+e>=u)&&n!==u-1&&delete i.label,f.push(i);return f},draw:function(t){var e=this,i=e.options;if(i.display){var r=e.ctx,u=o.global,d=i.ticks.minor,c=i.ticks.major||d,h=i.gridLines,f=i.scaleLabel,g=0!==e.labelRotation,p=e.isHorizontal(),v=d.autoSkip?e._autoSkip(e.getTicks()):e.getTicks(),m=l.valueOrDefault(d.fontColor,u.defaultFontColor),b=n(d),x=l.valueOrDefault(c.fontColor,u.defaultFontColor),y=n(c),k=h.drawTicks?h.tickMarkLength:0,w=l.valueOrDefault(f.fontColor,u.defaultFontColor),M=n(f),S=l.options.toPadding(f.padding),C=l.toRadians(e.labelRotation),_=[],D="right"===i.position?e.left:e.right-k,I="right"===i.position?e.left+k:e.right,P="bottom"===i.position?e.top:e.bottom-k,A="bottom"===i.position?e.top+k:e.bottom;if(l.each(v,function(n,o){if(!l.isNullOrUndef(n.label)){var r,s,c,f,m=n.label;o===e.zeroLineIndex&&i.offset===h.offsetGridLines?(r=h.zeroLineWidth,s=h.zeroLineColor,c=h.zeroLineBorderDash,f=h.zeroLineBorderDashOffset):(r=l.valueAtIndexOrDefault(h.lineWidth,o),s=l.valueAtIndexOrDefault(h.color,o),c=l.valueOrDefault(h.borderDash,u.borderDash),f=l.valueOrDefault(h.borderDashOffset,u.borderDashOffset));var b,x,y,w,M,S,T,F,O,R,L="middle",z="middle",B=d.padding;if(p){var W=k+B;"bottom"===i.position?(z=g?"middle":"top",L=g?"right":"center",R=e.top+W):(z=g?"middle":"bottom",L=g?"left":"center",R=e.bottom-W);var N=a(e,o,h.offsetGridLines&&v.length>1);N1);H0)n=t.stepSize;else{var o=i.niceNum(e.max-e.min,!1);n=i.niceNum(o/(t.maxTicks-1),!0)}var r=Math.floor(e.min/n)*n,l=Math.ceil(e.max/n)*n;t.min&&t.max&&t.stepSize&&i.almostWhole((t.max-t.min)/t.stepSize,n/1e3)&&(r=t.min,l=t.max);var s=(l-r)/n;s=i.almostEquals(s,Math.round(s),n/1e3)?Math.round(s):Math.ceil(s),a.push(void 0!==t.min?t.min:r);for(var u=1;u3?n[2]-n[1]:n[1]-n[0];Math.abs(a)>1&&t!==Math.floor(t)&&(a=t-Math.floor(t));var o=i.log10(Math.abs(a)),r="";if(0!==t){var l=-1*Math.floor(o);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var a=t/Math.pow(10,Math.floor(i.log10(t)));return 0===t?"0":1===a||2===a||5===a||0===e||e===n.length-1?t.toExponential():""}}}},{45:45}],35:[function(t,e,n){"use strict";var i=t(25),a=t(26),o=t(45);i._set("global",{tooltips:{enabled:!0,custom:null,mode:"nearest",position:"average",intersect:!0,backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,caretPadding:2,caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,callbacks:{beforeTitle:o.noop,title:function(t,e){var n="",i=e.labels,a=i?i.length:0;if(t.length>0){var o=t[0];o.xLabel?n=o.xLabel:a>0&&o.indexi.height-e.height&&(r="bottom");var l,s,u,d,c,h=(a.left+a.right)/2,f=(a.top+a.bottom)/2;"center"===r?(l=function(t){return t<=h},s=function(t){return t>h}):(l=function(t){return t<=e.width/2},s=function(t){return t>=i.width-e.width/2}),u=function(t){return t+e.width>i.width},d=function(t){return t-e.width<0},c=function(t){return t<=f?"top":"bottom"},l(n.x)?(o="left",u(n.x)&&(o="center",r=c(n.y))):s(n.x)&&(o="right",d(n.x)&&(o="center",r=c(n.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:o,yAlign:g.yAlign?g.yAlign:r}}function d(t,e,n){var i=t.x,a=t.y,o=t.caretSize,r=t.caretPadding,l=t.cornerRadius,s=n.xAlign,u=n.yAlign,d=o+r,c=l+r;return"right"===s?i-=e.width:"center"===s&&(i-=e.width/2),"top"===u?a+=d:a-="bottom"===u?e.height+d:e.height/2,"center"===u?"left"===s?i+=d:"right"===s&&(i-=d):"left"===s?i-=c:"right"===s&&(i+=c),{x:i,y:a}}t.Tooltip=a.extend({initialize:function(){this._model=l(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options.callbacks,i=e.beforeTitle.apply(t,arguments),a=e.title.apply(t,arguments),o=e.afterTitle.apply(t,arguments),r=[];return r=n(r,i),r=n(r,a),r=n(r,o)},getBeforeBody:function(){var t=this._options.callbacks.beforeBody.apply(this,arguments);return o.isArray(t)?t:void 0!==t?[t]:[]},getBody:function(t,e){var i=this,a=i._options.callbacks,r=[];return o.each(t,function(t){var o={before:[],lines:[],after:[]};n(o.before,a.beforeLabel.call(i,t,e)),n(o.lines,a.label.call(i,t,e)),n(o.after,a.afterLabel.call(i,t,e)),r.push(o)}),r},getAfterBody:function(){var t=this._options.callbacks.afterBody.apply(this,arguments);return o.isArray(t)?t:void 0!==t?[t]:[]},getFooter:function(){var t=this,e=t._options.callbacks,i=e.beforeFooter.apply(t,arguments),a=e.footer.apply(t,arguments),o=e.afterFooter.apply(t,arguments),r=[];return r=n(r,i),r=n(r,a),r=n(r,o)},update:function(e){var n,i,a=this,c=a._options,h=a._model,f=a._model=l(c),g=a._active,p=a._data,v={xAlign:h.xAlign,yAlign:h.yAlign},m={x:h.x,y:h.y},b={width:h.width,height:h.height},x={x:h.caretX,y:h.caretY};if(g.length){f.opacity=1;var y=[],k=[];x=t.Tooltip.positioners[c.position].call(a,g,a._eventPosition);var w=[];for(n=0,i=g.length;n0&&i.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,o=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&o&&(this.drawBackground(i,e,t,n,a),i.x+=e.xPadding,i.y+=e.yPadding,this.drawTitle(i,e,t,a),this.drawBody(i,e,t,a),this.drawFooter(i,e,t,a))}},handleEvent:function(t){var e=this,n=e._options,i=!1;if(e._lastActive=e._lastActive||[],"mouseout"===t.type?e._active=[]:e._active=e._chart.getElementsAtEventForMode(t,n.mode,n),!(i=!o.arrayEquals(e._active,e._lastActive)))return!1;if(e._lastActive=e._active,n.enabled||n.custom){e._eventPosition={x:t.x,y:t.y};var a=e._model;e.update(!0),e.pivot(),i|=a.x!==e._model.x||a.y!==e._model.y}return i}}),t.Tooltip.positioners={average:function(t){if(!t.length)return!1;var e,n,i=0,a=0,o=0;for(e=0,n=t.length;es;)a-=2*Math.PI;for(;a=l&&a<=s,d=r>=n.innerRadius&&r<=n.outerRadius;return u&&d}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t=this._chart.ctx,e=this._view,n=e.startAngle,i=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,n,i),t.arc(e.x,e.y,e.innerRadius,i,n,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})},{25:25,26:26,45:45}],37:[function(t,e,n){"use strict";var i=t(25),a=t(26),o=t(45),r=i.global;i._set("global",{elements:{line:{tension:.4,backgroundColor:r.defaultColor,borderWidth:3,borderColor:r.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0}}}),e.exports=a.extend({draw:function(){var t,e,n,i,a=this,l=a._view,s=a._chart.ctx,u=l.spanGaps,d=a._children.slice(),c=r.elements.line,h=-1;for(a._loop&&d.length&&d.push(d[0]),s.save(),s.lineCap=l.borderCapStyle||c.borderCapStyle,s.setLineDash&&s.setLineDash(l.borderDash||c.borderDash),s.lineDashOffset=l.borderDashOffset||c.borderDashOffset,s.lineJoin=l.borderJoinStyle||c.borderJoinStyle,s.lineWidth=l.borderWidth||c.borderWidth,s.strokeStyle=l.borderColor||r.defaultColor,s.beginPath(),h=-1,t=0;te?1:-1,r=1,l=u.borderSkipped||"left"):(e=u.x-u.width/2,n=u.x+u.width/2,i=u.y,o=1,r=(a=u.base)>i?1:-1,l=u.borderSkipped||"bottom"),d){var c=Math.min(Math.abs(e-n),Math.abs(i-a)),h=(d=d>c?c:d)/2,f=e+("left"!==l?h*o:0),g=n+("right"!==l?-h*o:0),p=i+("top"!==l?h*r:0),v=a+("bottom"!==l?-h*r:0);f!==g&&(i=p,a=v),p!==v&&(e=f,n=g)}s.beginPath(),s.fillStyle=u.backgroundColor,s.strokeStyle=u.borderColor,s.lineWidth=d;var m=[[e,a],[e,i],[n,i],[n,a]],b=["bottom","left","top","right"].indexOf(l,0);-1===b&&(b=0);var x=t(0);s.moveTo(x[0],x[1]);for(var y=1;y<4;y++)x=t(y),s.lineTo(x[0],x[1]);s.fill(),d&&s.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var n=!1;if(this._view){var i=a(this);n=t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom}return n},inLabelRange:function(t,e){var n=this;if(!n._view)return!1;var o=a(n);return i(n)?t>=o.left&&t<=o.right:e>=o.top&&e<=o.bottom},inXRange:function(t){var e=a(this);return t>=e.left&&t<=e.right},inYRange:function(t){var e=a(this);return t>=e.top&&t<=e.bottom},getCenterPoint:function(){var t,e,n=this._view;return i(this)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return t.width*Math.abs(t.y-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})},{25:25,26:26}],40:[function(t,e,n){"use strict";e.exports={},e.exports.Arc=t(36),e.exports.Line=t(37),e.exports.Point=t(38),e.exports.Rectangle=t(39)},{36:36,37:37,38:38,39:39}],41:[function(t,e,n){"use strict";var i=t(42),n=e.exports={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,o){if(o){var r=Math.min(o,i/2),l=Math.min(o,a/2);t.moveTo(e+r,n),t.lineTo(e+i-r,n),t.quadraticCurveTo(e+i,n,e+i,n+l),t.lineTo(e+i,n+a-l),t.quadraticCurveTo(e+i,n+a,e+i-r,n+a),t.lineTo(e+r,n+a),t.quadraticCurveTo(e,n+a,e,n+a-l),t.lineTo(e,n+l),t.quadraticCurveTo(e,n,e+r,n)}else t.rect(e,n,i,a)},drawPoint:function(t,e,n,i,a){var o,r,l,s,u,d;if(!e||"object"!=typeof e||"[object HTMLImageElement]"!==(o=e.toString())&&"[object HTMLCanvasElement]"!==o){if(!(isNaN(n)||n<=0)){switch(e){default:t.beginPath(),t.arc(i,a,n,0,2*Math.PI),t.closePath(),t.fill();break;case"triangle":t.beginPath(),u=(r=3*n/Math.sqrt(3))*Math.sqrt(3)/2,t.moveTo(i-r/2,a+u/3),t.lineTo(i+r/2,a+u/3),t.lineTo(i,a-2*u/3),t.closePath(),t.fill();break;case"rect":d=1/Math.SQRT2*n,t.beginPath(),t.fillRect(i-d,a-d,2*d,2*d),t.strokeRect(i-d,a-d,2*d,2*d);break;case"rectRounded":var c=n/Math.SQRT2,h=i-c,f=a-c,g=Math.SQRT2*n;t.beginPath(),this.roundedRect(t,h,f,g,g,n/2),t.closePath(),t.fill();break;case"rectRot":d=1/Math.SQRT2*n,t.beginPath(),t.moveTo(i-d,a),t.lineTo(i,a+d),t.lineTo(i+d,a),t.lineTo(i,a-d),t.closePath(),t.fill();break;case"cross":t.beginPath(),t.moveTo(i,a+n),t.lineTo(i,a-n),t.moveTo(i-n,a),t.lineTo(i+n,a),t.closePath();break;case"crossRot":t.beginPath(),l=Math.cos(Math.PI/4)*n,s=Math.sin(Math.PI/4)*n,t.moveTo(i-l,a-s),t.lineTo(i+l,a+s),t.moveTo(i-l,a+s),t.lineTo(i+l,a-s),t.closePath();break;case"star":t.beginPath(),t.moveTo(i,a+n),t.lineTo(i,a-n),t.moveTo(i-n,a),t.lineTo(i+n,a),l=Math.cos(Math.PI/4)*n,s=Math.sin(Math.PI/4)*n,t.moveTo(i-l,a-s),t.lineTo(i+l,a+s),t.moveTo(i-l,a+s),t.lineTo(i+l,a-s),t.closePath();break;case"line":t.beginPath(),t.moveTo(i-n,a),t.lineTo(i+n,a),t.closePath();break;case"dash":t.beginPath(),t.moveTo(i,a),t.lineTo(i+n,a),t.closePath()}t.stroke()}}else t.drawImage(e,i-e.width/2,a-e.height/2,e.width,e.height)},clipArea:function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},unclipArea:function(t){t.restore()},lineTo:function(t,e,n,i){if(n.steppedLine)return"after"===n.steppedLine&&!i||"after"!==n.steppedLine&&i?t.lineTo(e.x,n.y):t.lineTo(n.x,e.y),void t.lineTo(n.x,n.y);n.tension?t.bezierCurveTo(i?e.controlPointPreviousX:e.controlPointNextX,i?e.controlPointPreviousY:e.controlPointNextY,i?n.controlPointNextX:n.controlPointPreviousX,i?n.controlPointNextY:n.controlPointPreviousY,n.x,n.y):t.lineTo(n.x,n.y)}};i.clear=n.clear,i.drawRoundedRectangle=function(t){t.beginPath(),n.roundedRect.apply(n,arguments),t.closePath()}},{42:42}],42:[function(t,e,n){"use strict";var i={noop:function(){},uid:function(){var t=0;return function(){return t++}}(),isNullOrUndef:function(t){return null===t||void 0===t},isArray:Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},isObject:function(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)},valueOrDefault:function(t,e){return void 0===t?e:t},valueAtIndexOrDefault:function(t,e,n){return i.valueOrDefault(i.isArray(t)?t[e]:t,n)},callback:function(t,e,n){if(t&&"function"==typeof t.call)return t.apply(n,e)},each:function(t,e,n,a){var o,r,l;if(i.isArray(t))if(r=t.length,a)for(o=r-1;o>=0;o--)e.call(n,t[o],o);else for(o=0;o=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-a.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*a.easeInBounce(2*t):.5*a.easeOutBounce(2*t-1)+.5}};e.exports={effects:a},i.easingEffects=a},{42:42}],44:[function(t,e,n){"use strict";var i=t(42);e.exports={toLineHeight:function(t,e){var n=(""+t).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);if(!n||"normal"===n[1])return 1.2*e;switch(t=+n[2],n[3]){case"px":return t;case"%":t/=100}return e*t},toPadding:function(t){var e,n,a,o;return i.isObject(t)?(e=+t.top||0,n=+t.right||0,a=+t.bottom||0,o=+t.left||0):e=n=a=o=+t||0,{top:e,right:n,bottom:a,left:o,height:e+a,width:o+n}},resolve:function(t,e,n){var a,o,r;for(a=0,o=t.length;a
';var a=e.childNodes[0],r=e.childNodes[1];e._reset=function(){a.scrollLeft=1e6,a.scrollTop=1e6,r.scrollLeft=1e6,r.scrollTop=1e6};var l=function(){e._reset(),t()};return o(a,"scroll",l.bind(a,"expand")),o(r,"scroll",l.bind(r,"shrink")),e}function c(t,e){var n=t[m]||(t[m]={}),i=n.renderProxy=function(t){t.animationName===y&&e()};v.each(k,function(e){o(t,e,i)}),n.reflow=!!t.offsetParent,t.classList.add(x)}function h(t){var e=t[m]||{},n=e.renderProxy;n&&(v.each(k,function(e){r(t,e,n)}),delete e.renderProxy),t.classList.remove(x)}function f(t,e,n){var i=t[m]||(t[m]={}),a=i.resizer=d(u(function(){if(i.resizer)return e(l("resize",n))}));c(t,function(){if(i.resizer){var e=t.parentNode;e&&e!==a.parentNode&&e.insertBefore(a,e.firstChild),a._reset()}})}function g(t){var e=t[m]||{},n=e.resizer;delete e.resizer,h(t),n&&n.parentNode&&n.parentNode.removeChild(n)}function p(t,e){var n=t._style||document.createElement("style");t._style||(t._style=n,e="/* Chart.js */\n"+e,n.setAttribute("type","text/css"),document.getElementsByTagName("head")[0].appendChild(n)),n.appendChild(document.createTextNode(e))}var v=t(45),m="$chartjs",b="chartjs-",x=b+"render-monitor",y=b+"render-animation",k=["animationstart","webkitAnimationStart"],w={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},M=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};e.exports={_enabled:"undefined"!=typeof window&&"undefined"!=typeof document,initialize:function(){var t="from{opacity:0.99}to{opacity:1}";p(this,"@-webkit-keyframes "+y+"{"+t+"}@keyframes "+y+"{"+t+"}."+x+"{-webkit-animation:"+y+" 0.001s;animation:"+y+" 0.001s;}")},acquireContext:function(t,e){"string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas);var n=t&&t.getContext&&t.getContext("2d");return n&&n.canvas===t?(a(t,e),n):null},releaseContext:function(t){var e=t.canvas;if(e[m]){var n=e[m].initial;["height","width"].forEach(function(t){var i=n[t];v.isNullOrUndef(i)?e.removeAttribute(t):e.setAttribute(t,i)}),v.each(n.style||{},function(t,n){e.style[n]=t}),e.width=e.width,delete e[m]}},addEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=n[m]||(n[m]={});o(i,e,(a.proxies||(a.proxies={}))[t.id+"_"+e]=function(e){n(s(e,t))})}else f(i,n,t)},removeEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=((n[m]||{}).proxies||{})[t.id+"_"+e];a&&r(i,e,a)}else g(i)}},v.addEvent=o,v.removeEvent=r},{45:45}],48:[function(t,e,n){"use strict";var i=t(45),a=t(46),o=t(47),r=o._enabled?o:a;e.exports=i.extend({initialize:function(){},acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},r)},{45:45,46:46,47:47}],49:[function(t,e,n){"use strict";var i=t(25),a=t(40),o=t(45);i._set("global",{plugins:{filler:{propagate:!0}}}),e.exports=function(){function t(t,e,n){var i,a=t._model||{},o=a.fill;if(void 0===o&&(o=!!a.backgroundColor),!1===o||null===o)return!1;if(!0===o)return"origin";if(i=parseFloat(o,10),isFinite(i)&&Math.floor(i)===i)return"-"!==o[0]&&"+"!==o[0]||(i=e+i),!(i===e||i<0||i>=n)&&i;switch(o){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return o;default:return!1}}function e(t){var e,n=t.el._model||{},i=t.el._scale||{},a=t.fill,o=null;if(isFinite(a))return null;if("start"===a?o=void 0===n.scaleBottom?i.bottom:n.scaleBottom:"end"===a?o=void 0===n.scaleTop?i.top:n.scaleTop:void 0!==n.scaleZero?o=n.scaleZero:i.getBasePosition?o=i.getBasePosition():i.getBasePixel&&(o=i.getBasePixel()),void 0!==o&&null!==o){if(void 0!==o.x&&void 0!==o.y)return o;if("number"==typeof o&&isFinite(o))return e=i.isHorizontal(),{x:e?o:null,y:e?null:o}}return null}function n(t,e,n){var i,a=t[e].fill,o=[e];if(!n)return a;for(;!1!==a&&-1===o.indexOf(a);){if(!isFinite(a))return a;if(!(i=t[a]))return!1;if(i.visible)return a;o.push(a),a=i.fill}return!1}function r(t){var e=t.fill,n="dataset";return!1===e?null:(isFinite(e)||(n="boundary"),d[n](t))}function l(t){return t&&!t.skip}function s(t,e,n,i,a){var r;if(i&&a){for(t.moveTo(e[0].x,e[0].y),r=1;r0;--r)o.canvas.lineTo(t,n[r],n[r-1],!0)}}function u(t,e,n,i,a,o){var r,u,d,c,h,f,g,p=e.length,v=i.spanGaps,m=[],b=[],x=0,y=0;for(t.beginPath(),r=0,u=p+!!o;r');for(var n=0;n '),t.data.datasets[n].label&&e.push(t.data.datasets[n].label),e.push("");return e.push(""),e.join("")}}),e.exports=function(t){function e(t,e){return t.usePointStyle?e*Math.SQRT2:t.boxWidth}function n(e,n){var i=new t.Legend({ctx:e.ctx,options:n,chart:e});r.configure(e,i,n),r.addBox(e,i),e.legend=i}var r=t.layoutService,l=o.noop;return t.Legend=a.extend({initialize:function(t){o.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:l,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:l,beforeSetDimensions:l,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:l,beforeBuildLabels:l,buildLabels:function(){var t=this,e=t.options.labels||{},n=o.callback(e.generateLabels,[t.chart],t)||[];e.filter&&(n=n.filter(function(n){return e.filter(n,t.chart.data)})),t.options.reverse&&n.reverse(),t.legendItems=n},afterBuildLabels:l,beforeFit:l,fit:function(){var t=this,n=t.options,a=n.labels,r=n.display,l=t.ctx,s=i.global,u=o.valueOrDefault,d=u(a.fontSize,s.defaultFontSize),c=u(a.fontStyle,s.defaultFontStyle),h=u(a.fontFamily,s.defaultFontFamily),f=o.fontString(d,c,h),g=t.legendHitBoxes=[],p=t.minSize,v=t.isHorizontal();if(v?(p.width=t.maxWidth,p.height=r?10:0):(p.width=r?10:0,p.height=t.maxHeight),r)if(l.font=f,v){var m=t.lineWidths=[0],b=t.legendItems.length?d+a.padding:0;l.textAlign="left",l.textBaseline="top",o.each(t.legendItems,function(n,i){var o=e(a,d)+d/2+l.measureText(n.text).width;m[m.length-1]+o+a.padding>=t.width&&(b+=d+a.padding,m[m.length]=t.left),g[i]={left:0,top:0,width:o,height:d},m[m.length-1]+=o+a.padding}),p.height+=b}else{var x=a.padding,y=t.columnWidths=[],k=a.padding,w=0,M=0,S=d+x;o.each(t.legendItems,function(t,n){var i=e(a,d)+d/2+l.measureText(t.text).width;M+S>p.height&&(k+=w+a.padding,y.push(w),w=0,M=0),w=Math.max(w,i),M+=S,g[n]={left:0,top:0,width:i,height:d}}),k+=w,y.push(w),p.width+=k}t.width=p.width,t.height=p.height},afterFit:l,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,n=t.options,a=n.labels,r=i.global,l=r.elements.line,s=t.width,u=t.lineWidths;if(n.display){var d,c=t.ctx,h=o.valueOrDefault,f=h(a.fontColor,r.defaultFontColor),g=h(a.fontSize,r.defaultFontSize),p=h(a.fontStyle,r.defaultFontStyle),v=h(a.fontFamily,r.defaultFontFamily),m=o.fontString(g,p,v);c.textAlign="left",c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=m;var b=e(a,g),x=t.legendHitBoxes,y=function(t,e,i){if(!(isNaN(b)||b<=0)){c.save(),c.fillStyle=h(i.fillStyle,r.defaultColor),c.lineCap=h(i.lineCap,l.borderCapStyle),c.lineDashOffset=h(i.lineDashOffset,l.borderDashOffset),c.lineJoin=h(i.lineJoin,l.borderJoinStyle),c.lineWidth=h(i.lineWidth,l.borderWidth),c.strokeStyle=h(i.strokeStyle,r.defaultColor);var a=0===h(i.lineWidth,l.borderWidth);if(c.setLineDash&&c.setLineDash(h(i.lineDash,l.borderDash)),n.labels&&n.labels.usePointStyle){var s=g*Math.SQRT2/2,u=s/Math.SQRT2,d=t+u,f=e+u;o.canvas.drawPoint(c,i.pointStyle,s,d,f)}else a||c.strokeRect(t,e,b,g),c.fillRect(t,e,b,g);c.restore()}},k=function(t,e,n,i){var a=g/2,o=b+a+t,r=e+a;c.fillText(n.text,o,r),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(o,r),c.lineTo(o+i,r),c.stroke())},w=t.isHorizontal();d=w?{x:t.left+(s-u[0])/2,y:t.top+a.padding,line:0}:{x:t.left+a.padding,y:t.top+a.padding,line:0};var M=g+a.padding;o.each(t.legendItems,function(e,n){var i=c.measureText(e.text).width,o=b+g/2+i,r=d.x,l=d.y;w?r+o>=s&&(l=d.y+=M,d.line++,r=d.x=t.left+(s-u[d.line])/2):l+M>t.bottom&&(r=d.x=r+t.columnWidths[d.line]+a.padding,l=d.y=t.top+a.padding,d.line++),y(r,l,e),x[n].left=r,x[n].top=l,k(r,l,e,i),w?d.x+=o+a.padding:d.y+=M})}},handleEvent:function(t){var e=this,n=e.options,i="mouseup"===t.type?"click":t.type,a=!1;if("mousemove"===i){if(!n.onHover)return}else{if("click"!==i)return;if(!n.onClick)return}var o=t.x,r=t.y;if(o>=e.left&&o<=e.right&&r>=e.top&&r<=e.bottom)for(var l=e.legendHitBoxes,s=0;s=u.left&&o<=u.left+u.width&&r>=u.top&&r<=u.top+u.height){if("click"===i){n.onClick.call(e,t.native,e.legendItems[s]),a=!0;break}if("mousemove"===i){n.onHover.call(e,t.native,e.legendItems[s]),a=!0;break}}}return a}}),{id:"legend",beforeInit:function(t){var e=t.options.legend;e&&n(t,e)},beforeUpdate:function(t){var e=t.options.legend,a=t.legend;e?(o.mergeIf(e,i.global.legend),a?(r.configure(t,a,e),a.options=e):n(t,e)):a&&(r.removeBox(t,a),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}}}},{25:25,26:26,45:45}],51:[function(t,e,n){"use strict";var i=t(25),a=t(26),o=t(45);i._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,lineHeight:1.2,padding:10,position:"top",text:"",weight:2e3}}),e.exports=function(t){function e(e,i){var a=new t.Title({ctx:e.ctx,options:i,chart:e});n.configure(e,a,i),n.addBox(e,a),e.titleBlock=a}var n=t.layoutService,r=o.noop;return t.Title=a.extend({initialize:function(t){var e=this;o.extend(e,t),e.legendHitBoxes=[]},beforeUpdate:r,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:r,beforeSetDimensions:r,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:r,beforeBuildLabels:r,buildLabels:r,afterBuildLabels:r,beforeFit:r,fit:function(){var t=this,e=o.valueOrDefault,n=t.options,a=n.display,r=e(n.fontSize,i.global.defaultFontSize),l=t.minSize,s=o.isArray(n.text)?n.text.length:1,u=o.options.toLineHeight(n.lineHeight,r),d=a?s*u+2*n.padding:0;t.isHorizontal()?(l.width=t.maxWidth,l.height=d):(l.width=d,l.height=t.maxHeight),t.width=l.width,t.height=l.height},afterFit:r,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=o.valueOrDefault,a=t.options,r=i.global;if(a.display){var l,s,u,d=n(a.fontSize,r.defaultFontSize),c=n(a.fontStyle,r.defaultFontStyle),h=n(a.fontFamily,r.defaultFontFamily),f=o.fontString(d,c,h),g=o.options.toLineHeight(a.lineHeight,d),p=g/2+a.padding,v=0,m=t.top,b=t.left,x=t.bottom,y=t.right;e.fillStyle=n(a.fontColor,r.defaultFontColor),e.font=f,t.isHorizontal()?(s=b+(y-b)/2,u=m+p,l=y-b):(s="left"===a.position?b+p:y-p,u=m+(x-m)/2,l=x-m,v=Math.PI*("left"===a.position?-.5:.5)),e.save(),e.translate(s,u),e.rotate(v),e.textAlign="center",e.textBaseline="middle";var k=a.text;if(o.isArray(k))for(var w=0,M=0;Me.max&&(e.max=i))})});e.min=isFinite(e.min)&&!isNaN(e.min)?e.min:0,e.max=isFinite(e.max)&&!isNaN(e.max)?e.max:1,this.handleTickRangeOptions()},getTickLimit:function(){var t,e=this,n=e.options.ticks;if(e.isHorizontal())t=Math.min(n.maxTicksLimit?n.maxTicksLimit:11,Math.ceil(e.width/50));else{var o=a.valueOrDefault(n.fontSize,i.global.defaultFontSize);t=Math.min(n.maxTicksLimit?n.maxTicksLimit:11,Math.ceil(e.height/(2*o)))}return t},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){var e,n=this,i=n.start,a=+n.getRightValue(t),o=n.end-i;return n.isHorizontal()?(e=n.left+n.width/o*(a-i),Math.round(e)):(e=n.bottom-n.height/o*(a-i),Math.round(e))},getValueForPixel:function(t){var e=this,n=e.isHorizontal(),i=n?e.width:e.height,a=(n?t-e.left:e.bottom-t)/i;return e.start+(e.end-e.start)*a},getPixelForTick:function(t){return this.getPixelForValue(this.ticksAsNumbers[t])}});t.scaleService.registerScaleType("linear",n,e)}},{25:25,34:34,45:45}],54:[function(t,e,n){"use strict";var i=t(45),a=t(34);e.exports=function(t){var e=i.noop;t.LinearScaleBase=t.Scale.extend({getRightValue:function(e){return"string"==typeof e?+e:t.Scale.prototype.getRightValue.call(this,e)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=i.sign(t.min),a=i.sign(t.max);n<0&&a<0?t.max=0:n>0&&a>0&&(t.min=0)}var o=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),o!==r&&t.min>=t.max&&(o?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:e,handleDirectionalChanges:e,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),o={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,stepSize:i.valueOrDefault(e.fixedStepSize,e.stepSize)},r=t.ticks=a.generators.linear(o,t);t.handleDirectionalChanges(),t.max=i.max(r),t.min=i.min(r),e.reverse?(r.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max)},convertTicksToLabels:function(){var e=this;e.ticksAsNumbers=e.ticks.slice(),e.zeroLineIndex=e.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(e)}})}},{34:34,45:45}],55:[function(t,e,n){"use strict";var i=t(45),a=t(34);e.exports=function(t){var e={position:"left",ticks:{callback:a.formatters.logarithmic}},n=t.Scale.extend({determineDataLimits:function(){function t(t){return s?t.xAxisID===e.id:t.yAxisID===e.id}var e=this,n=e.options,a=n.ticks,o=e.chart,r=o.data.datasets,l=i.valueOrDefault,s=e.isHorizontal();e.min=null,e.max=null,e.minNotZero=null;var u=n.stacked;if(void 0===u&&i.each(r,function(e,n){if(!u){var i=o.getDatasetMeta(n);o.isDatasetVisible(n)&&t(i)&&void 0!==i.stack&&(u=!0)}}),n.stacked||u){var d={};i.each(r,function(a,r){var l=o.getDatasetMeta(r),s=[l.type,void 0===n.stacked&&void 0===l.stack?r:"",l.stack].join(".");o.isDatasetVisible(r)&&t(l)&&(void 0===d[s]&&(d[s]=[]),i.each(a.data,function(t,i){var a=d[s],o=+e.getRightValue(t);isNaN(o)||l.data[i].hidden||(a[i]=a[i]||0,n.relativePoints?a[i]=100:a[i]+=o)}))}),i.each(d,function(t){var n=i.min(t),a=i.max(t);e.min=null===e.min?n:Math.min(e.min,n),e.max=null===e.max?a:Math.max(e.max,a)})}else i.each(r,function(n,a){var r=o.getDatasetMeta(a);o.isDatasetVisible(a)&&t(r)&&i.each(n.data,function(t,n){var i=+e.getRightValue(t);isNaN(i)||r.data[n].hidden||(null===e.min?e.min=i:ie.max&&(e.max=i),0!==i&&(null===e.minNotZero||ia?{start:e-n-5,end:e}:{start:e,end:e+n+5}}function s(t){var i,o,s,u=n(t),d=Math.min(t.height/2,t.width/2),c={r:t.width,l:0,t:t.height,b:0},h={};t.ctx.font=u.font,t._pointLabelSizes=[];var f=e(t);for(i=0;ic.r&&(c.r=v.end,h.r=g),m.startc.b&&(c.b=m.end,h.b=g)}t.setReductions(d,c,h)}function u(t){var e=Math.min(t.height/2,t.width/2);t.drawingArea=Math.round(e),t.setCenterPoint(0,0,0,0)}function d(t){return 0===t||180===t?"center":t<180?"left":"right"}function c(t,e,n,i){if(a.isArray(e))for(var o=n.y,r=1.5*i,l=0;l270||t<90)&&(n.y-=e.h)}function f(t){var i=t.ctx,o=a.valueOrDefault,r=t.options,l=r.angleLines,s=r.pointLabels;i.lineWidth=l.lineWidth,i.strokeStyle=l.color;var u=t.getDistanceFromCenterForValue(r.ticks.reverse?t.min:t.max),f=n(t);i.textBaseline="top";for(var g=e(t)-1;g>=0;g--){if(l.display){var p=t.getPointPosition(g,u);i.beginPath(),i.moveTo(t.xCenter,t.yCenter),i.lineTo(p.x,p.y),i.stroke(),i.closePath()}if(s.display){var m=t.getPointPosition(g,u+5),b=o(s.fontColor,v.defaultFontColor);i.font=f.font,i.fillStyle=b;var x=t.getIndexAngle(g),y=a.toDegrees(x);i.textAlign=d(y),h(y,t._pointLabelSizes[g],m),c(i,t.pointLabels[g]||"",m,f.size)}}}function g(t,n,i,o){var r=t.ctx;if(r.strokeStyle=a.valueAtIndexOrDefault(n.color,o-1),r.lineWidth=a.valueAtIndexOrDefault(n.lineWidth,o-1),t.options.gridLines.circular)r.beginPath(),r.arc(t.xCenter,t.yCenter,i,0,2*Math.PI),r.closePath(),r.stroke();else{var l=e(t);if(0===l)return;r.beginPath();var s=t.getPointPosition(0,i);r.moveTo(s.x,s.y);for(var u=1;u0&&n>0?e:0)},draw:function(){var t=this,e=t.options,n=e.gridLines,i=e.ticks,o=a.valueOrDefault;if(e.display){var r=t.ctx,l=this.getIndexAngle(0),s=o(i.fontSize,v.defaultFontSize),u=o(i.fontStyle,v.defaultFontStyle),d=o(i.fontFamily,v.defaultFontFamily),c=a.fontString(s,u,d);a.each(t.ticks,function(e,a){if(a>0||i.reverse){var u=t.getDistanceFromCenterForValue(t.ticksAsNumbers[a]);if(n.display&&0!==a&&g(t,n,u,a),i.display){var d=o(i.fontColor,v.defaultFontColor);if(r.font=c,r.save(),r.translate(t.xCenter,t.yCenter),r.rotate(l),i.showLabelBackdrop){var h=r.measureText(e).width;r.fillStyle=i.backdropColor,r.fillRect(-h/2-i.backdropPaddingX,-u-s/2-i.backdropPaddingY,h+2*i.backdropPaddingX,s+2*i.backdropPaddingY)}r.textAlign="center",r.textBaseline="middle",r.fillStyle=d,r.fillText(e,0,-u),r.restore()}}}),(e.angleLines.display||e.pointLabels.display)&&f(t)}}});t.scaleService.registerScaleType("radialLinear",b,m)}},{25:25,34:34,45:45}],57:[function(t,e,n){"use strict";function i(t,e){return t-e}function a(t){var e,n,i,a={},o=[];for(e=0,n=t.length;ee&&l=0&&r<=l;){if(i=r+l>>1,a=t[i-1]||null,o=t[i],!a)return{lo:null,hi:o};if(o[e]n))return{lo:a,hi:o};l=i-1}}return{lo:o,hi:null}}function l(t,e,n,i){var a=r(t,e,n),o=a.lo?a.hi?a.lo:t[t.length-2]:t[0],l=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=l[e]-o[e],u=s?(n-o[e])/s:0,d=(l[i]-o[i])*u;return o[i]+d}function s(t,e){var n=e.parser,i=e.parser||e.format;return"function"==typeof n?n(t):"string"==typeof t&&"string"==typeof i?m(t,i):(t instanceof m||(t=m(t)),t.isValid()?t:"function"==typeof i?i(t):t)}function u(t,e){if(x.isNullOrUndef(t))return null;var n=e.options.time,i=s(e.getRightValue(t),n);return i.isValid()?(n.round&&i.startOf(n.round),i.valueOf()):null}function d(t,e,n,i){var a,o,r,l=e-t,s=w[n],u=s.size,d=s.steps;if(!d)return Math.ceil(l/((i||1)*u));for(a=0,o=d.length;a=M.indexOf(e);a--)if(o=M[a],w[o].common&&r.as(o)>=t.length)return o;return M[e?M.indexOf(e):0]}function f(t){for(var e=M.indexOf(t)+1,n=M.length;e1?e[1]:i,r=e[0],s=(l(t,"time",o,"pos")-l(t,"time",r,"pos"))/2),a.time.max||(o=e[e.length-1],r=e.length>1?e[e.length-2]:n,u=(l(t,"time",o,"pos")-l(t,"time",r,"pos"))/2)),{left:s,right:u}}function v(t,e){var n,i,a,o,r=[];for(n=0,i=t.length;n=a&&n<=r&&c.push(n);return i.min=a,i.max=r,i._unit=s.unit||h(c,s.minUnit,i.min,i.max),i._majorUnit=f(i._unit),i._table=o(i._timestamps.data,a,r,l.distribution),i._offsets=p(i._table,c,a,r,l),v(c,i._majorUnit)},getLabelForIndex:function(t,e){var n=this,i=n.chart.data,a=n.options.time,o=i.labels&&t=0&&t postsJSON
+ values[1] // => commentsJSON
+
+ return values;
+ });
+ ```
+
+ @class Promise
+ @param {function} resolver
+ Useful for tooling.
+ @constructor
+*/
+function Promise$3(resolver) {
+ this[PROMISE_ID] = nextId();
+ this._result = this._state = undefined;
+ this._subscribers = [];
+
+ if (noop !== resolver) {
+ typeof resolver !== 'function' && needsResolver();
+ this instanceof Promise$3 ? initializePromise(this, resolver) : needsNew();
+ }
+}
+
+Promise$3.all = all$1;
+Promise$3.race = race$1;
+Promise$3.resolve = resolve$1;
+Promise$3.reject = reject$1;
+Promise$3._setScheduler = setScheduler;
+Promise$3._setAsap = setAsap;
+Promise$3._asap = asap;
+
+Promise$3.prototype = {
+ constructor: Promise$3,
+
+ /**
+ The primary way of interacting with a promise is through its `then` method,
+ which registers callbacks to receive either a promise's eventual value or the
+ reason why the promise cannot be fulfilled.
+
+ ```js
+ findUser().then(function(user){
+ // user is available
+ }, function(reason){
+ // user is unavailable, and you are given the reason why
+ });
+ ```
+
+ Chaining
+ --------
+
+ The return value of `then` is itself a promise. This second, 'downstream'
+ promise is resolved with the return value of the first promise's fulfillment
+ or rejection handler, or rejected if the handler throws an exception.
+
+ ```js
+ findUser().then(function (user) {
+ return user.name;
+ }, function (reason) {
+ return 'default name';
+ }).then(function (userName) {
+ // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+ // will be `'default name'`
+ });
+
+ findUser().then(function (user) {
+ throw new Error('Found user, but still unhappy');
+ }, function (reason) {
+ throw new Error('`findUser` rejected and we're unhappy');
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+ // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+ });
+ ```
+ If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+
+ ```js
+ findUser().then(function (user) {
+ throw new PedagogicalException('Upstream error');
+ }).then(function (value) {
+ // never reached
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // The `PedgagocialException` is propagated all the way down to here
+ });
+ ```
+
+ Assimilation
+ ------------
+
+ Sometimes the value you want to propagate to a downstream promise can only be
+ retrieved asynchronously. This can be achieved by returning a promise in the
+ fulfillment or rejection handler. The downstream promise will then be pending
+ until the returned promise is settled. This is called *assimilation*.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // The user's comments are now available
+ });
+ ```
+
+ If the assimliated promise rejects, then the downstream promise will also reject.
+
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // If `findCommentsByAuthor` fulfills, we'll have the value here
+ }, function (reason) {
+ // If `findCommentsByAuthor` rejects, we'll have the reason here
+ });
+ ```
+
+ Simple Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ let result;
+
+ try {
+ result = findResult();
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+ findResult(function(result, err){
+ if (err) {
+ // failure
+ } else {
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findResult().then(function(result){
+ // success
+ }, function(reason){
+ // failure
+ });
+ ```
+
+ Advanced Example
+ --------------
+
+ Synchronous Example
+
+ ```javascript
+ let author, books;
+
+ try {
+ author = findAuthor();
+ books = findBooksByAuthor(author);
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+
+ Errback Example
+
+ ```js
+
+ function foundBooks(books) {
+
+ }
+
+ function failure(reason) {
+
+ }
+
+ findAuthor(function(author, err){
+ if (err) {
+ failure(err);
+ // failure
+ } else {
+ try {
+ findBoooksByAuthor(author, function(books, err) {
+ if (err) {
+ failure(err);
+ } else {
+ try {
+ foundBooks(books);
+ } catch(reason) {
+ failure(reason);
+ }
+ }
+ });
+ } catch(error) {
+ failure(err);
+ }
+ // success
+ }
+ });
+ ```
+
+ Promise Example;
+
+ ```javascript
+ findAuthor().
+ then(findBooksByAuthor).
+ then(function(books){
+ // found books
+ }).catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method then
+ @param {Function} onFulfilled
+ @param {Function} onRejected
+ Useful for tooling.
+ @return {Promise}
+ */
+ then: then,
+
+ /**
+ `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+ as the catch block of a try/catch statement.
+
+ ```js
+ function findAuthor(){
+ throw new Error('couldn't find that author');
+ }
+
+ // synchronous
+ try {
+ findAuthor();
+ } catch(reason) {
+ // something went wrong
+ }
+
+ // async with promises
+ findAuthor().catch(function(reason){
+ // something went wrong
+ });
+ ```
+
+ @method catch
+ @param {Function} onRejection
+ Useful for tooling.
+ @return {Promise}
+ */
+ 'catch': function _catch(onRejection) {
+ return this.then(null, onRejection);
+ }
+};
+
+/*global self*/
+function polyfill$1() {
+ var local = undefined;
+
+ if (typeof global !== 'undefined') {
+ local = global;
+ } else if (typeof self !== 'undefined') {
+ local = self;
+ } else {
+ try {
+ local = Function('return this')();
+ } catch (e) {
+ throw new Error('polyfill failed because global object is unavailable in this environment');
+ }
+ }
+
+ var P = local.Promise;
+
+ if (P) {
+ var promiseToString = null;
+ try {
+ promiseToString = Object.prototype.toString.call(P.resolve());
+ } catch (e) {
+ // silently ignored
+ }
+
+ if (promiseToString === '[object Promise]' && !P.cast) {
+ return;
+ }
+ }
+
+ local.Promise = Promise$3;
+}
+
+// Strange compat..
+Promise$3.polyfill = polyfill$1;
+Promise$3.Promise = Promise$3;
+
+Promise$3.polyfill();
+
+return Promise$3;
+
+})));
+
+//# sourceMappingURL=es6-promise.auto.map
diff --git a/wp-content/plugins/imagify/assets/js/es6-promise.auto.min.js b/wp-content/plugins/imagify/assets/js/es6-promise.auto.min.js
new file mode 100644
index 00000000..ba34fa7c
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/es6-promise.auto.min.js
@@ -0,0 +1 @@
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.ES6Promise=e()}(this,function(){"use strict";function t(t){var e=typeof t;return null!==t&&("object"===e||"function"===e)}function e(t){return"function"==typeof t}function n(t){I=t}function r(t){J=t}function o(){return function(){return process.nextTick(a)}}function i(){return"undefined"!=typeof H?function(){H(a)}:c()}function s(){var t=0,e=new V(a),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){return t.port2.postMessage(0)}}function c(){var t=setTimeout;return function(){return t(a,1)}}function a(){for(var t=0;t tag.
+ */
+ insertBulkActionTags: function() {
+ var bulkActions = '' + w.imagifyFiles.labels.bulkActionsOptimize + ' ';
+
+ if ( w.imagifyFiles.backupOption || $( '.file-has-backup' ).length ) {
+ // If the backup option is enabled, or if we have items that can be restored.
+ bulkActions += '' + w.imagifyFiles.labels.bulkActionsRestore + ' ';
+ }
+
+ $( '.bulkactions select[name="action"] option:first-child, .bulkactions select[name="action2"] option:first-child' ).after( bulkActions );
+ },
+
+ /**
+ * Process one of these actions: bulk restore, bulk optimize, or bulk refresh-status.
+ *
+ * @param {object} e Event.
+ */
+ processBulkAction: function( e ) {
+ var value = $( this ).prev( 'select' ).val(),
+ action;
+
+ if ( 'imagify-bulk-optimize' !== value && 'imagify-bulk-restore' !== value && 'imagify-bulk-refresh-status' !== value ) {
+ return;
+ }
+
+ e.preventDefault();
+
+ action = value.replace( 'imagify-bulk-', '' );
+
+ $( 'input[name="bulk_select[]"]:checked' ).closest( 'tr' ).find( '.button-imagify-' + action ).each( function ( index, el ) {
+ setTimeout( function() {
+ $( el ).trigger( 'click.imagify' );
+ }, index * 500 );
+ } );
+ },
+
+ // Optimization ============================================================================
+
+ /**
+ * Process one of these actions: optimize, re-optimize, restore, or refresh-status.
+ *
+ * @param {object} e Event.
+ */
+ processOptimization: function( e ) {
+ var $button = $( this ),
+ $row = $button.closest( 'tr' ),
+ $checkbox = $row.find( '.check-column [type="checkbox"]' ),
+ id = imagify.filesList.sanitizeId( $checkbox.val() ),
+ context = w.imagifyFiles.context,
+ $parent, href, processingTemplate;
+
+ e.preventDefault();
+
+ if ( imagify.filesList.isItemLocked( context, id ) ) {
+ return;
+ }
+
+ imagify.filesList.lockItem( context, id );
+
+ href = $button.attr( 'href' );
+ processingTemplate = w.imagify.template( 'imagify-button-processing' );
+ $parent = $button.closest( '.column-actions, .column-status' );
+
+ $parent.html( processingTemplate( {
+ label: $button.data( 'processing-label' )
+ } ) );
+
+ $.get( href.replace( 'admin-post.php', 'admin-ajax.php' ) )
+ .done( function( r ) {
+ if ( ! r.success ) {
+ if ( r.data && r.data.row ) {
+ $row.html( '' + r.data.row + ' ' );
+ } else {
+ $parent.html( r.data );
+ }
+
+ $row.find( '.check-column [type="checkbox"]' ).prop( 'checked', false );
+
+ imagify.filesList.unlockItem( context, id );
+ return;
+ }
+
+ if ( r.data && r.data.columns ) {
+ // The work is done.
+ w.imagify.filesList.displayProcessResult( context, id, r.data.columns );
+ } else {
+ // Still processing in background: we're waiting for the result by poking Imagifybeat.
+ // Set the Imagifybeat interval to 15 seconds.
+ w.imagify.beat.interval( 15 );
+ }
+ } );
+ },
+
+ // Imagifybeat =============================================================================
+
+ /**
+ * Send the media IDs and their status to Imagifybeat.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ addToImagifybeat: function ( e, data ) {
+ var $boxes = $( '.wp-list-table.imagify-files .check-column [name="bulk_select[]"]' );
+
+ if ( ! $boxes.length ) {
+ return;
+ }
+
+ data[ w.imagifyFiles.imagifybeatID ] = {};
+
+ $boxes.each( function() {
+ var id = w.imagify.filesList.sanitizeId( this.value ),
+ context = w.imagifyFiles.context,
+ locked = w.imagify.filesList.isItemLocked( context, id ) ? 1 : 0;
+
+ data[ w.imagifyFiles.imagifybeatID ][ context ] = data[ w.imagifyFiles.imagifybeatID ][ context ] || {};
+ data[ w.imagifyFiles.imagifybeatID ][ context ][ '_' + id ] = locked;
+ } );
+ },
+
+ /**
+ * Listen for the custom event "imagifybeat-tick" on $(document).
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ processImagifybeat: function ( e, data ) {
+ if ( typeof data[ w.imagifyFiles.imagifybeatID ] === 'undefined' ) {
+ return;
+ }
+
+ $.each( data[ w.imagifyFiles.imagifybeatID ], function( contextId, columns ) {
+ var context, id;
+
+ context = $.trim( contextId ).match( /^(.+)_(\d+)$/ );
+
+ if ( ! context ) {
+ return;
+ }
+
+ id = w.imagify.filesList.sanitizeId( context[2] );
+ context = w.imagify.filesList.sanitizeContext( context[1] );
+
+ if ( context !== w.imagifyFiles.context ) {
+ return;
+ }
+
+ w.imagify.filesList.displayProcessResult( context, id, columns );
+ } );
+ },
+
+ // DOM manipulation tools ==================================================================
+
+ /**
+ * Display a successful process result.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ * @param {string} columns A list of HTML, keyed by column name.
+ */
+ displayProcessResult: function( context, id, columns ) {
+ var $row = w.imagify.filesList.getContainers( id );
+
+ $.each( columns, function( k, v ) {
+ $row.children( '.column-' + k ).html( v );
+ } );
+
+ $row.find( '.check-column [type="checkbox"]' ).prop( 'checked', false );
+
+ w.imagify.filesList.unlockItem( context, id );
+
+ if ( ! w.imagify.filesList.working.length ) {
+ // Work is done.
+ // Reset Imagifybeat interval.
+ w.imagify.beat.resetInterval();
+ }
+ },
+
+ /**
+ * Get all containers matching the given id.
+ *
+ * @param {int} id The media ID.
+ * @return {object} A jQuery collection.
+ */
+ getContainers: function( id ) {
+ return $( '.wp-list-table.imagify-files .check-column [name="bulk_select[]"][value="' + id + '"]' ).closest( 'tr' );
+ },
+
+ // Sanitization ============================================================================
+
+ /**
+ * Sanitize a media ID.
+ *
+ * @param {int|string} id A media ID.
+ * @return {int}
+ */
+ sanitizeId: function( id ) {
+ return parseInt( id, 10 );
+ },
+
+ /**
+ * Sanitize a media context.
+ *
+ * @param {string} context A media context.
+ * @return {string}
+ */
+ sanitizeContext: function( context ) {
+ context = context.replace( '/[^a-z0-9_-]/gi', '' ).toLowerCase();
+ return context ? context : 'wp';
+ },
+
+ // Locks ===================================================================================
+
+ /**
+ * Lock an item.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ */
+ lockItem: function( context, id ) {
+ if ( ! this.isItemLocked( context, id ) ) {
+ this.working.push( context + '_' + id );
+ }
+ },
+
+ /**
+ * Unlock an item.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ */
+ unlockItem: function( context, id ) {
+ var name = context + '_' + id,
+ i = _.indexOf( this.working, name );
+
+ if ( i > -1 ) {
+ this.working.splice( i, 1 );
+ }
+ },
+
+ /**
+ * Tell if an item is locked.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ * @return {bool}
+ */
+ isItemLocked: function( context, id ) {
+ return _.indexOf( this.working, context + '_' + id ) > -1;
+ }
+ };
+
+ w.imagify.filesList.init();
+
+} )(jQuery, document, window);
+
+
+(function(w) { // eslint-disable-line no-shadow, no-shadow-restricted-names
+
+ /**
+ * requestAnimationFrame polyfill by Erik Möller.
+ * Fixes from Paul Irish and Tino Zijdel.
+ * MIT license - http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating.
+ */
+ var lastTime = 0,
+ vendors = ['ms', 'moz', 'webkit', 'o'];
+
+ for ( var x = 0; x < vendors.length && ! w.requestAnimationFrame; ++x ) {
+ w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'];
+ w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || w[vendors[x] + 'CancelRequestAnimationFrame'];
+ }
+
+ if ( ! w.requestAnimationFrame ) {
+ w.requestAnimationFrame = function( callback ) {
+ var currTime = new Date().getTime(),
+ timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ),
+ id = setTimeout( function() {
+ callback( currTime + timeToCall );
+ }, timeToCall );
+
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+ }
+
+ if ( ! w.cancelAnimationFrame ) {
+ w.cancelAnimationFrame = function( id ) {
+ clearTimeout( id );
+ };
+ }
+
+})(window);
+
+
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ /**
+ * LazyLoad images in the list.
+ */
+ var lazyImages = $( '#imagify-files-list-form' ).find( '[data-lazy-src]' ),
+ lazyTimer;
+
+ function lazyLoadThumbnails() {
+ w.cancelAnimationFrame( lazyTimer );
+ lazyTimer = w.requestAnimationFrame( lazyLoadThumbnailsCallback ); // eslint-disable-line no-use-before-define
+ }
+
+ function lazyLoadThumbnailsCallback() {
+ var $w = $( w ),
+ winScroll = $w.scrollTop(),
+ winHeight = $w.outerHeight();
+
+ $.each( lazyImages, function() {
+ var $image = $( this ),
+ imgTop = $image.offset().top,
+ imgBottom = imgTop + $image.outerHeight(),
+ screenTopThresholded = winScroll - 150,
+ screenBottomThresholded = winScroll + winHeight + 150,
+ src;
+
+ lazyImages = lazyImages.not( $image );
+
+ if ( ! lazyImages.length ) {
+ $w.off( 'scroll resize orientationchange', lazyLoadThumbnails );
+ }
+
+ /**
+ * Hidden images that are above the fold and below the top, are reported as:
+ * - offset: window scroll,
+ * - height: 0,
+ * (at least in Firefox).
+ * That's why I use <= and >=.
+ *
+ * 150 is the threshold.
+ */
+ if ( imgBottom >= screenTopThresholded && imgTop <= screenBottomThresholded ) {
+ src = $image.attr( 'data-lazy-src' );
+
+ if ( undefined !== src && src ) {
+ $image.attr( 'src', src ).removeAttr( 'data-lazy-src' );
+ }
+
+ $image.next( 'noscript' ).remove();
+ }
+ } );
+ }
+
+ if ( lazyImages.length ) {
+ $( w ).on( 'scroll resize orientationchange', lazyLoadThumbnails );
+ lazyLoadThumbnailsCallback();
+ }
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/files-list.min.js b/wp-content/plugins/imagify/assets/js/files-list.min.js
new file mode 100644
index 00000000..4b342dc0
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/files-list.min.js
@@ -0,0 +1 @@
+window.imagify.drawMeAChart=function(a){a.each(function(){var a=parseInt(jQuery(this).closest(".imagify-chart").next(".imagify-chart-value").text(),10);new window.imagify.Chart(this,{type:"doughnut",data:{datasets:[{data:[a,100-a],backgroundColor:["#00B3D3","#D8D8D8"],borderColor:"#fff",borderWidth:1}]},options:{legend:{display:!1},events:[],animation:{easing:"easeOutBounce"},tooltips:{enabled:!1},responsive:!1}})})},function(a,b,c,d){c.imagify.filesList={working:[],init:function(){var d,e=a(b);a(c).on("canvasprinted.imagify",this.updateChart).trigger("canvasprinted.imagify"),this.insertBulkActionTags(),a("#doaction, #doaction2").on("click.imagify",this.processBulkAction),e.on("click.imagify",".button-imagify-optimize, .button-imagify-manual-reoptimize, .button-imagify-generate-webp, .button-imagify-delete-webp, .button-imagify-restore, .button-imagify-refresh-status",this.processOptimization),e.on("imagifybeat-send",this.addToImagifybeat),e.on("imagifybeat-tick",this.processImagifybeat),d=a(".wp-list-table.imagify-files .button-imagify-processing"),d.length&&(d.closest("tr").find('.check-column [name="bulk_select[]"]').each(function(){var a=c.imagify.filesList.sanitizeId(this.value);c.imagify.filesList.lockItem(c.imagifyFiles.context,a)}),c.imagify.beat.interval(15))},updateChart:function(b,d){var e;d=d||".imagify-consumption-chart",e=a(d),c.imagify.drawMeAChart(e),e.closest(".imagify-datas-list").siblings(".imagify-datas-details").hide()},insertBulkActionTags:function(){var b=''+c.imagifyFiles.labels.bulkActionsOptimize+" ";(c.imagifyFiles.backupOption||a(".file-has-backup").length)&&(b+=''+c.imagifyFiles.labels.bulkActionsRestore+" "),a('.bulkactions select[name="action"] option:first-child, .bulkactions select[name="action2"] option:first-child').after(b)},processBulkAction:function(b){var c,d=a(this).prev("select").val();"imagify-bulk-optimize"!==d&&"imagify-bulk-restore"!==d&&"imagify-bulk-refresh-status"!==d||(b.preventDefault(),c=d.replace("imagify-bulk-",""),a('input[name="bulk_select[]"]:checked').closest("tr").find(".button-imagify-"+c).each(function(b,c){setTimeout(function(){a(c).trigger("click.imagify")},500*b)}))},processOptimization:function(b){var d,e,f,g=a(this),h=g.closest("tr"),i=h.find('.check-column [type="checkbox"]'),j=imagify.filesList.sanitizeId(i.val()),k=c.imagifyFiles.context;b.preventDefault(),imagify.filesList.isItemLocked(k,j)||(imagify.filesList.lockItem(k,j),e=g.attr("href"),f=c.imagify.template("imagify-button-processing"),d=g.closest(".column-actions, .column-status"),d.html(f({label:g.data("processing-label")})),a.get(e.replace("admin-post.php","admin-ajax.php")).done(function(a){if(!a.success)return a.data&&a.data.row?h.html(''+a.data.row+" "):d.html(a.data),h.find('.check-column [type="checkbox"]').prop("checked",!1),void imagify.filesList.unlockItem(k,j);a.data&&a.data.columns?c.imagify.filesList.displayProcessResult(k,j,a.data.columns):c.imagify.beat.interval(15)}))},addToImagifybeat:function(b,d){var e=a('.wp-list-table.imagify-files .check-column [name="bulk_select[]"]');e.length&&(d[c.imagifyFiles.imagifybeatID]={},e.each(function(){var a=c.imagify.filesList.sanitizeId(this.value),b=c.imagifyFiles.context,e=c.imagify.filesList.isItemLocked(b,a)?1:0;d[c.imagifyFiles.imagifybeatID][b]=d[c.imagifyFiles.imagifybeatID][b]||{},d[c.imagifyFiles.imagifybeatID][b]["_"+a]=e}))},processImagifybeat:function(b,d){void 0!==d[c.imagifyFiles.imagifybeatID]&&a.each(d[c.imagifyFiles.imagifybeatID],function(b,d){var e,f;(e=a.trim(b).match(/^(.+)_(\d+)$/))&&(f=c.imagify.filesList.sanitizeId(e[2]),(e=c.imagify.filesList.sanitizeContext(e[1]))===c.imagifyFiles.context&&c.imagify.filesList.displayProcessResult(e,f,d))})},displayProcessResult:function(b,d,e){var f=c.imagify.filesList.getContainers(d);a.each(e,function(a,b){f.children(".column-"+a).html(b)}),f.find('.check-column [type="checkbox"]').prop("checked",!1),c.imagify.filesList.unlockItem(b,d),c.imagify.filesList.working.length||c.imagify.beat.resetInterval()},getContainers:function(b){return a('.wp-list-table.imagify-files .check-column [name="bulk_select[]"][value="'+b+'"]').closest("tr")},sanitizeId:function(a){return parseInt(a,10)},sanitizeContext:function(a){return(a=a.replace("/[^a-z0-9_-]/gi","").toLowerCase())||"wp"},lockItem:function(a,b){this.isItemLocked(a,b)||this.working.push(a+"_"+b)},unlockItem:function(a,b){var c=a+"_"+b,d=_.indexOf(this.working,c);d>-1&&this.working.splice(d,1)},isItemLocked:function(a,b){return _.indexOf(this.working,a+"_"+b)>-1}},c.imagify.filesList.init()}(jQuery,document,window),function(a){for(var b=0,c=["ms","moz","webkit","o"],d=0;d=l&&j<=m&&(c=i.attr("data-lazy-src"),d!==c&&c&&i.attr("src",c).removeAttr("data-lazy-src"),i.next("noscript").remove())})}var g,h=a("#imagify-files-list-form").find("[data-lazy-src]");h.length&&(a(c).on("scroll resize orientationchange",e),f())}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/imagify-gulp.js b/wp-content/plugins/imagify/assets/js/imagify-gulp.js
new file mode 100644
index 00000000..4b43204a
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/imagify-gulp.js
@@ -0,0 +1,400 @@
+/**
+ * Library that handles the bulk optimization processes.
+ *
+ * @requires jQuery
+ */
+window.imagify = window.imagify || {};
+
+/* eslint-disable no-underscore-dangle, consistent-this */
+(function($, d, w) {
+
+ /**
+ * Construct the optimizer.
+ *
+ * @param {object} settings {
+ * Optimizer settings:
+ *
+ * @type {string} groupID Group ID, like 'library' or 'custom-folders'.
+ * @type {string} context Context within this group, like 'wp' or 'custom-folders' (yes, again).
+ * @type {int} level Optimization level: 0 to 2.
+ * @type {int} bufferSize Number of parallel optimizations: usually 4.
+ * @type {string} ajaxUrl URL to request to optimize.
+ * @type {object} files Files to optimize: media ID as key (prefixed with an underscore), file URL as value.
+ * @type {string} defaultThumb A default thumbnail URL.
+ * @type {string} doneEvent Name of the event to listen to know when optimizations end.
+ * @type {array} imageExtensions A list of supported image extensions (only images).
+ * }
+ */
+ w.imagify.Optimizer = function ( settings ) {
+ // Settings.
+ this.groupID = settings.groupID;
+ this.context = settings.context;
+ this.level = settings.level;
+ this.bufferSize = settings.bufferSize || 1;
+ this.ajaxUrl = settings.ajaxUrl;
+ this.files = settings.files;
+ this.defaultThumb = settings.defaultThumb;
+ this.doneEvent = settings.doneEvent;
+
+ if ( settings.imageExtensions ) {
+ this.imageExtensions = settings.imageExtensions;
+ } else {
+ this.imageExtensions = [ 'jpg', 'jpeg', 'jpe', 'png', 'gif' ];
+ }
+
+ /**
+ * An array of media IDs (prefixed with an underscore).
+ */
+ this.prefixedMediaIDs = Object.keys( this.files );
+ /**
+ * An array of medias currently being optimized: {
+ * @type {int} mediaID The media ID.
+ * @type {string} filename The file name.
+ * @type {string} thumbnail The file thumbnail URL.
+ * }
+ */
+ this.currentItems = [];
+
+ // Internal counters.
+ this.totalMedia = this.prefixedMediaIDs.length;
+ this.processedMedia = 0;
+
+ // Global stats.
+ this.globalOriginalSize = 0;
+ this.globalOptimizedSize = 0;
+ this.globalGain = 0;
+ this.globalPercent = 0;
+
+ // Callbacks.
+ this._before = function () {};
+ this._each = function () {};
+ this._done = function () {};
+
+ // Listen to the "optimization done" event.
+ if ( this.totalMedia && this.doneEvent ) {
+ $( w ).on( this.doneEvent, { _this: this }, this.processedCallback );
+ }
+ };
+
+ /**
+ * Callback to trigger before each media optimization.
+ *
+ * @param {callable} fnc A callback.
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.before = function( fnc ) {
+ this._before = fnc;
+ return this;
+ };
+
+ /**
+ * Callback to trigger after each media optimization.
+ *
+ * @param {callable} fnc A callback.
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.each = function( fnc ) {
+ this._each = fnc;
+ return this;
+ };
+
+ /**
+ * Callback to trigger all media optimizations have been done.
+ *
+ * @param {callable} fnc A callback.
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.done = function( fnc ) {
+ this._done = fnc;
+ return this;
+ };
+
+ /**
+ * Launch optimizations.
+ *
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.run = function() {
+ var chunkLength = this.prefixedMediaIDs.length > this.bufferSize ? this.bufferSize : this.prefixedMediaIDs.length,
+ i;
+
+ for ( i = 0; i < chunkLength; i++ ) {
+ this.processNext();
+ }
+
+ return this;
+ };
+
+ /**
+ * Launch next optimization.
+ *
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.processNext = function() {
+ if ( this.prefixedMediaIDs.length ) {
+ this.process( this.prefixedMediaIDs.shift() );
+ }
+
+ return this;
+ };
+
+ /**
+ * Launch an optimization.
+ *
+ * @param {string} prefixedId A media ID, prefixed with an underscore.
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.process = function( prefixedId ) {
+ var _this = this,
+ fileURL = this.files[ prefixedId ],
+ data = {
+ mediaID: parseInt( prefixedId.toString().substr( 1 ), 10 ),
+ filename: this.files[ prefixedId ].split( '/' ).pop(),
+ thumbnail: this.defaultThumb
+ },
+ extension = data.filename.split( '.' ).pop().toLowerCase(),
+ regexp = new RegExp( '^' + this.imageExtensions.join( '|' ).toLowerCase() + '$' ),
+ image;
+
+ if ( ! extension.match( regexp ) ) {
+ // Not an image.
+ this.currentItems.push( data );
+ this._before( data );
+ this.send( data );
+ return this;
+ }
+
+ // Create a thumbnail and send the ajax request.
+ image = new Image();
+
+ image.onerror = function () {
+ _this.currentItems.push( data );
+ _this._before( data );
+ _this.send( data );
+ };
+
+ image.onload = function () {
+ var maxWidth = 33,
+ maxHeight = 33,
+ imageWidth = image.width,
+ imageHeight = image.height,
+ newHeight = 0,
+ newWidth = 0,
+ topOffset = 0,
+ leftOffset = 0,
+ canvas = null,
+ ctx = null;
+
+ if ( imageWidth < imageHeight ) {
+ // Portrait.
+ newWidth = maxWidth;
+ newHeight = newWidth * imageHeight / imageWidth;
+ topOffset = ( maxHeight - newHeight ) / 2;
+ } else {
+ // Landscape.
+ newHeight = maxHeight;
+ newWidth = newHeight * imageWidth / imageHeight;
+ leftOffset = ( maxWidth - newWidth ) / 2;
+ }
+
+ canvas = d.createElement( 'canvas' );
+
+ canvas.width = maxWidth;
+ canvas.height = maxHeight;
+
+ ctx = canvas.getContext( '2d' );
+ ctx.drawImage( this, leftOffset, topOffset, newWidth, newHeight );
+
+ try {
+ data.thumbnail = canvas.toDataURL( 'image/jpeg' );
+ } catch ( e ) {
+ data.thumbnail = _this.defaultThumb;
+ }
+
+ canvas = null;
+ ctx = null;
+ image = null;
+
+ _this.currentItems.push( data );
+ _this._before( data );
+ _this.send( data );
+ };
+
+ image.src = fileURL;
+
+ return this;
+ };
+
+ /**
+ * Do the ajax request.
+ *
+ * @param {object} data {
+ * The data:
+ *
+ * @type {int} mediaID The media ID.
+ * @type {string} filename The file name.
+ * @type {string} thumbnail The file thumbnail URL.
+ * }
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.send = function( data ) {
+ var _this = this,
+ defaultResponse = {
+ success: false,
+ mediaID: data.mediaID,
+ groupID: this.groupID,
+ context: this.context,
+ filename: data.filename,
+ thumbnail: data.thumbnail,
+ status: 'error',
+ error: ''
+ };
+
+ $.post( {
+ url: this.ajaxUrl,
+ data: {
+ media_id: data.mediaID,
+ context: this.context,
+ optimization_level: this.level
+ },
+ dataType: 'json'
+ } )
+ .done( function( response ) {
+ if ( response.success ) {
+ return;
+ }
+
+ defaultResponse.error = response.data.error;
+
+ _this.processed( defaultResponse );
+ } )
+ .fail( function( jqXHR ) {
+ if ( 200 === jqXHR.status ) {
+ defaultResponse.error = jqXHR.responseText.replace( /.*<\/h1>\n*/, '' );
+ } else {
+ defaultResponse.error = jqXHR.statusText;
+ }
+
+ _this.processed( defaultResponse );
+ } );
+
+ return this;
+ };
+
+ /**
+ * Callback triggered when an optimization is complete.
+ *
+ * @param {object} e jQuery's Event object.
+ * @param {object} item {
+ * The response:
+ *
+ * @type {int} mediaID The media ID.
+ * @type {string} context The context.
+ * }
+ */
+ w.imagify.Optimizer.prototype.processedCallback = function( e, item ) {
+ var _this = e.data._this;
+
+ if ( item.context !== _this.context ) {
+ return;
+ }
+
+ if ( ! item.mediaID || typeof _this.files[ '_' + item.mediaID ] === 'undefined' ) {
+ return;
+ }
+
+ item.groupID = _this.groupID;
+
+ if ( ! _this.currentItems.length ) {
+ // Trouble.
+ _this.processed( item );
+ return;
+ }
+
+ $.each( _this.currentItems, function( i, mediaData ) {
+ if ( item.mediaID === mediaData.mediaID ) {
+ item.filename = mediaData.filename;
+ item.thumbnail = mediaData.thumbnail;
+ return false;
+ }
+ } );
+
+ _this.processed( item );
+ };
+
+ /**
+ * After a media has been processed.
+ *
+ * @param {object} response {
+ * The response:
+ *
+ * @type {bool} success Whether the optimization succeeded or not ("already optimized" is a success).
+ * @type {int} mediaID The media ID.
+ * @type {string} groupID The group ID.
+ * @type {string} context The context.
+ * @type {string} filename The file name.
+ * @type {string} thumbnail The file thumbnail URL.
+ * @type {string} status The status, like 'optimized', 'already-optimized', 'over-quota', 'error'.
+ * @type {string} error The error message.
+ * }
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.processed = function( response ) {
+ var currentItems = this.currentItems;
+
+ if ( currentItems.length ) {
+ // Remove this media from the "current" list.
+ $.each( currentItems, function( i, mediaData ) {
+ if ( response.mediaID === mediaData.mediaID ) {
+ currentItems.splice( i, 1 );
+ return false;
+ }
+ } );
+
+ this.currentItems = currentItems;
+ }
+
+ // Update stats.
+ if ( response.success && 'already-optimized' !== response.status ) {
+ this.globalOriginalSize += response.originalOverallSize;
+ this.globalOptimizedSize += response.newOverallSize;
+ this.globalGain += response.overallSaving;
+ this.globalPercent = ( 100 - this.globalOptimizedSize / this.globalOptimizedSize * 100 ).toFixed( 2 );
+ }
+
+ ++this.processedMedia;
+ response.progress = Math.floor( this.processedMedia / this.totalMedia * 100 );
+
+ this._each( response );
+
+ if ( this.prefixedMediaIDs.length ) {
+ this.processNext();
+ } else if ( this.totalMedia === this.processedMedia ) {
+ this._done( {
+ globalOriginalSize: this.globalOriginalSize,
+ globalOptimizedSize: this.globalOptimizedSize,
+ globalGain: this.globalGain
+ } );
+ }
+
+ return this;
+ };
+
+ /**
+ * Stop the process.
+ *
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.stopProcess = function() {
+ this.files = {};
+ this.prefixedMediaIDs = [];
+ this.currentItems = [];
+
+ if ( this.doneEvent ) {
+ $( w ).off( this.doneEvent, this.processedCallback );
+ }
+
+ return this;
+ };
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/imagify-gulp.min.js b/wp-content/plugins/imagify/assets/js/imagify-gulp.min.js
new file mode 100644
index 00000000..25d40d79
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/imagify-gulp.min.js
@@ -0,0 +1,396 @@
+/**
+ * Library that handles the bulk optimization processes.
+ *
+ * @requires jQuery
+ */
+window.imagify = window.imagify || {};
+
+/* eslint-disable no-underscore-dangle, consistent-this */
+(function($, d, w) {
+
+ /**
+ * Construct the optimizer.
+ *
+ * @param {object} settings {
+ * Optimizer settings:
+ *
+ * @type {string} groupID Group ID, like 'library' or 'custom-folders'.
+ * @type {string} context Context within this group, like 'wp' or 'custom-folders' (yes, again).
+ * @type {int} level Optimization level: 0 to 2.
+ * @type {int} bufferSize Number of parallel optimizations: usually 4.
+ * @type {string} ajaxUrl URL to request to optimize.
+ * @type {object} files Files to optimize: media ID as key (prefixed with an underscore), file URL as value.
+ * @type {string} defaultThumb A default thumbnail URL.
+ * @type {string} doneEvent Name of the event to listen to know when optimizations end.
+ * @type {array} imageExtensions A list of supported image extensions (only images).
+ * }
+ */
+ w.imagify.Optimizer = function ( settings ) {
+ // Settings.
+ this.groupID = settings.groupID;
+ this.context = settings.context;
+ this.level = settings.level;
+ this.bufferSize = settings.bufferSize || 1;
+ this.ajaxUrl = settings.ajaxUrl;
+ this.files = settings.files;
+ this.defaultThumb = settings.defaultThumb;
+ this.doneEvent = settings.doneEvent;
+
+ if ( settings.imageExtensions ) {
+ this.imageExtensions = settings.imageExtensions;
+ } else {
+ this.imageExtensions = [ 'jpg', 'jpeg', 'jpe', 'png', 'gif' ];
+ }
+
+ /**
+ * An array of media IDs (prefixed with an underscore).
+ */
+ this.prefixedMediaIDs = Object.keys( this.files );
+ /**
+ * An array of medias currently being optimized: {
+ * @type {int} mediaID The media ID.
+ * @type {string} filename The file name.
+ * @type {string} thumbnail The file thumbnail URL.
+ * }
+ */
+ this.currentItems = [];
+
+ // Internal counters.
+ this.totalMedia = this.prefixedMediaIDs.length;
+ this.processedMedia = 0;
+
+ // Global stats.
+ this.globalOriginalSize = 0;
+ this.globalOptimizedSize = 0;
+ this.globalGain = 0;
+ this.globalPercent = 0;
+
+ // Callbacks.
+ this._before = function () {};
+ this._each = function () {};
+ this._done = function () {};
+
+ // Listen to the "optimization done" event.
+ if ( this.totalMedia && this.doneEvent ) {
+ $( w ).on( this.doneEvent, { _this: this }, this.processedCallback );
+ }
+ };
+
+ /**
+ * Callback to trigger before each media optimization.
+ *
+ * @param {callable} fnc A callback.
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.before = function( fnc ) {
+ this._before = fnc;
+ return this;
+ };
+
+ /**
+ * Callback to trigger after each media optimization.
+ *
+ * @param {callable} fnc A callback.
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.each = function( fnc ) {
+ this._each = fnc;
+ return this;
+ };
+
+ /**
+ * Callback to trigger all media optimizations have been done.
+ *
+ * @param {callable} fnc A callback.
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.done = function( fnc ) {
+ this._done = fnc;
+ return this;
+ };
+
+ /**
+ * Launch optimizations.
+ *
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.run = function() {
+ var chunkLength = this.prefixedMediaIDs.length > this.bufferSize ? this.bufferSize : this.prefixedMediaIDs.length,
+ i;
+
+ for ( i = 0; i < chunkLength; i++ ) {
+ this.processNext();
+ }
+
+ return this;
+ };
+
+ /**
+ * Launch next optimization.
+ *
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.processNext = function() {
+ if ( this.prefixedMediaIDs.length ) {
+ this.process( this.prefixedMediaIDs.shift() );
+ }
+
+ return this;
+ };
+
+ /**
+ * Launch an optimization.
+ *
+ * @param {string} prefixedId A media ID, prefixed with an underscore.
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.process = function( prefixedId ) {
+ var _this = this,
+ fileURL = this.files[ prefixedId ],
+ data = {
+ mediaID: parseInt( prefixedId.toString().substr( 1 ), 10 ),
+ filename: this.files[ prefixedId ].split( '/' ).pop(),
+ thumbnail: this.defaultThumb
+ },
+ extension = data.filename.split( '.' ).pop().toLowerCase(),
+ regexp = new RegExp( '^' + this.imageExtensions.join( '|' ).toLowerCase() + '$' ),
+ image;
+
+ if ( ! extension.match( regexp ) ) {
+ // Not an image.
+ this.currentItems.push( data );
+ this._before( data );
+ this.send( data );
+ return this;
+ }
+
+ // Create a thumbnail and send the ajax request.
+ image = new Image();
+
+ image.onerror = function () {
+ _this.currentItems.push( data );
+ _this._before( data );
+ _this.send( data );
+ };
+
+ image.onload = function () {
+ var maxWidth = 33,
+ maxHeight = 33,
+ imageWidth = image.width,
+ imageHeight = image.height,
+ newHeight = 0,
+ newWidth = 0,
+ topOffset = 0,
+ leftOffset = 0,
+ canvas = null,
+ ctx = null;
+
+ if ( imageWidth < imageHeight ) {
+ // Portrait.
+ newWidth = maxWidth;
+ newHeight = newWidth * imageHeight / imageWidth;
+ topOffset = ( maxHeight - newHeight ) / 2;
+ } else {
+ // Landscape.
+ newHeight = maxHeight;
+ newWidth = newHeight * imageWidth / imageHeight;
+ leftOffset = ( maxWidth - newWidth ) / 2;
+ }
+
+ canvas = d.createElement( 'canvas' );
+
+ canvas.width = maxWidth;
+ canvas.height = maxHeight;
+
+ ctx = canvas.getContext( '2d' );
+ ctx.drawImage( this, leftOffset, topOffset, newWidth, newHeight );
+
+ try {
+ data.thumbnail = canvas.toDataURL( 'image/jpeg' );
+ } catch ( e ) {
+ data.thumbnail = _this.defaultThumb;
+ }
+
+ canvas = null;
+ ctx = null;
+ image = null;
+
+ _this.currentItems.push( data );
+ _this._before( data );
+ _this.send( data );
+ };
+
+ image.src = fileURL;
+
+ return this;
+ };
+
+ /**
+ * Do the ajax request.
+ *
+ * @param {object} data {
+ * The data:
+ *
+ * @type {int} mediaID The media ID.
+ * @type {string} filename The file name.
+ * @type {string} thumbnail The file thumbnail URL.
+ * }
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.send = function( data ) {
+ var _this = this,
+ defaultResponse = {
+ success: false,
+ mediaID: data.mediaID,
+ groupID: this.groupID,
+ context: this.context,
+ filename: data.filename,
+ thumbnail: data.thumbnail,
+ status: 'error',
+ error: ''
+ };
+
+ $.post( {
+ url: this.ajaxUrl,
+ data: {
+ media_id: data.mediaID,
+ context: this.context,
+ optimization_level: this.level
+ },
+ dataType: 'json'
+ } )
+ .done( function( response ) {
+ if ( response.success ) {
+ return;
+ }
+
+ defaultResponse.error = response.data.error;
+
+ _this.processed( defaultResponse );
+ } )
+ .fail( function( jqXHR ) {
+ defaultResponse.error = jqXHR.statusText;
+
+ _this.processed( defaultResponse );
+ } );
+
+ return this;
+ };
+
+ /**
+ * Callback triggered when an optimization is complete.
+ *
+ * @param {object} e jQuery's Event object.
+ * @param {object} item {
+ * The response:
+ *
+ * @type {int} mediaID The media ID.
+ * @type {string} context The context.
+ * }
+ */
+ w.imagify.Optimizer.prototype.processedCallback = function( e, item ) {
+ var _this = e.data._this;
+
+ if ( item.context !== _this.context ) {
+ return;
+ }
+
+ if ( ! item.mediaID || typeof _this.files[ '_' + item.mediaID ] === 'undefined' ) {
+ return;
+ }
+
+ item.groupID = _this.groupID;
+
+ if ( ! _this.currentItems.length ) {
+ // Trouble.
+ _this.processed( item );
+ return;
+ }
+
+ $.each( _this.currentItems, function( i, mediaData ) {
+ if ( item.mediaID === mediaData.mediaID ) {
+ item.filename = mediaData.filename;
+ item.thumbnail = mediaData.thumbnail;
+ return false;
+ }
+ } );
+
+ _this.processed( item );
+ };
+
+ /**
+ * After a media has been processed.
+ *
+ * @param {object} response {
+ * The response:
+ *
+ * @type {bool} success Whether the optimization succeeded or not ("already optimized" is a success).
+ * @type {int} mediaID The media ID.
+ * @type {string} groupID The group ID.
+ * @type {string} context The context.
+ * @type {string} filename The file name.
+ * @type {string} thumbnail The file thumbnail URL.
+ * @type {string} status The status, like 'optimized', 'already-optimized', 'over-quota', 'error'.
+ * @type {string} error The error message.
+ * }
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.processed = function( response ) {
+ var currentItems = this.currentItems;
+
+ if ( currentItems.length ) {
+ // Remove this media from the "current" list.
+ $.each( currentItems, function( i, mediaData ) {
+ if ( response.mediaID === mediaData.mediaID ) {
+ currentItems.splice( i, 1 );
+ return false;
+ }
+ } );
+
+ this.currentItems = currentItems;
+ }
+
+ // Update stats.
+ if ( response.success && 'already-optimized' !== response.status ) {
+ this.globalOriginalSize += response.originalOverallSize;
+ this.globalOptimizedSize += response.newOverallSize;
+ this.globalGain += response.overallSaving;
+ this.globalPercent = ( 100 - this.globalOptimizedSize / this.globalOptimizedSize * 100 ).toFixed( 2 );
+ }
+
+ ++this.processedMedia;
+ response.progress = Math.floor( this.processedMedia / this.totalMedia * 100 );
+
+ this._each( response );
+
+ if ( this.prefixedMediaIDs.length ) {
+ this.processNext();
+ } else if ( this.totalMedia === this.processedMedia ) {
+ this._done( {
+ globalOriginalSize: this.globalOriginalSize,
+ globalOptimizedSize: this.globalOptimizedSize,
+ globalGain: this.globalGain
+ } );
+ }
+
+ return this;
+ };
+
+ /**
+ * Stop the process.
+ *
+ * @return this
+ */
+ w.imagify.Optimizer.prototype.stopProcess = function() {
+ this.files = {};
+ this.prefixedMediaIDs = [];
+ this.currentItems = [];
+
+ if ( this.doneEvent ) {
+ $( w ).off( this.doneEvent, this.processedCallback );
+ }
+
+ return this;
+ };
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/jquery.event.move.js b/wp-content/plugins/imagify/assets/js/jquery.event.move.js
new file mode 100644
index 00000000..0ef0748e
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/jquery.event.move.js
@@ -0,0 +1,584 @@
+// DOM.event.move
+//
+// 2.0.1
+//
+// Stephen Band
+//
+// Triggers 'movestart', 'move' and 'moveend' events after
+// mousemoves following a mousedown cross a distance threshold,
+// similar to the native 'dragstart', 'drag' and 'dragend' events.
+// Move events are throttled to animation frames. Move event objects
+// have the properties:
+//
+// pageX:
+// pageY: Page coordinates of pointer.
+// startX:
+// startY: Page coordinates of pointer at movestart.
+// distX:
+// distY: Distance the pointer has moved since movestart.
+// deltaX:
+// deltaY: Distance the finger has moved since last event.
+// velocityX:
+// velocityY: Average velocity over last few events.
+
+
+(function(fn) {
+ if (typeof define === 'function' && define.amd) {
+ define([], fn);
+ } else if ((typeof module !== "undefined" && module !== null) && module.exports) {
+ module.exports = fn;
+ } else {
+ fn();
+ }
+})(function(){
+ var assign = Object.assign || window.jQuery && jQuery.extend;
+
+ // Number of pixels a pressed pointer travels before movestart
+ // event is fired.
+ var threshold = 8;
+
+ // Shim for requestAnimationFrame, falling back to timer. See:
+ // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ var requestFrame = (function(){
+ return (
+ window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(fn, element){
+ return window.setTimeout(function(){
+ fn();
+ }, 25);
+ }
+ );
+ })();
+
+ var ignoreTags = {
+ textarea: true,
+ input: true,
+ select: true,
+ button: true
+ };
+
+ var mouseevents = {
+ move: 'mousemove',
+ cancel: 'mouseup dragstart',
+ end: 'mouseup'
+ };
+
+ var touchevents = {
+ move: 'touchmove',
+ cancel: 'touchend',
+ end: 'touchend'
+ };
+
+ var rspaces = /\s+/;
+
+
+ // DOM Events
+
+ var eventOptions = { bubbles: true, cancelable: true };
+
+ var eventsSymbol = Symbol('events');
+
+ function createEvent(type) {
+ return new CustomEvent(type, eventOptions);
+ }
+
+ function getEvents(node) {
+ return node[eventsSymbol] || (node[eventsSymbol] = {});
+ }
+
+ function on(node, types, fn, data, selector) {
+ types = types.split(rspaces);
+
+ var events = getEvents(node);
+ var i = types.length;
+ var handlers, type;
+
+ function handler(e) { fn(e, data); }
+
+ while (i--) {
+ type = types[i];
+ handlers = events[type] || (events[type] = []);
+ handlers.push([fn, handler]);
+ node.addEventListener(type, handler);
+ }
+ }
+
+ function off(node, types, fn, selector) {
+ types = types.split(rspaces);
+
+ var events = getEvents(node);
+ var i = types.length;
+ var type, handlers, k;
+
+ if (!events) { return; }
+
+ while (i--) {
+ type = types[i];
+ handlers = events[type];
+ if (!handlers) { continue; }
+ k = handlers.length;
+ while (k--) {
+ if (handlers[k][0] === fn) {
+ node.removeEventListener(type, handlers[k][1]);
+ handlers.splice(k, 1);
+ }
+ }
+ }
+ }
+
+ function trigger(node, type, properties) {
+ // Don't cache events. It prevents you from triggering an event of a
+ // given type from inside the handler of another event of that type.
+ var event = createEvent(type);
+ if (properties) { assign(event, properties); }
+ node.dispatchEvent(event);
+ }
+
+
+ // Constructors
+
+ function Timer(fn){
+ var callback = fn,
+ active = false,
+ running = false;
+
+ function trigger(time) {
+ if (active){
+ callback();
+ requestFrame(trigger);
+ running = true;
+ active = false;
+ }
+ else {
+ running = false;
+ }
+ }
+
+ this.kick = function(fn) {
+ active = true;
+ if (!running) { trigger(); }
+ };
+
+ this.end = function(fn) {
+ var cb = callback;
+
+ if (!fn) { return; }
+
+ // If the timer is not running, simply call the end callback.
+ if (!running) {
+ fn();
+ }
+ // If the timer is running, and has been kicked lately, then
+ // queue up the current callback and the end callback, otherwise
+ // just the end callback.
+ else {
+ callback = active ?
+ function(){ cb(); fn(); } :
+ fn ;
+
+ active = true;
+ }
+ };
+ }
+
+
+ // Functions
+
+ function noop() {}
+
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+
+ function isIgnoreTag(e) {
+ return !!ignoreTags[e.target.tagName.toLowerCase()];
+ }
+
+ function isPrimaryButton(e) {
+ // Ignore mousedowns on any button other than the left (or primary)
+ // mouse button, or when a modifier key is pressed.
+ return (e.which === 1 && !e.ctrlKey && !e.altKey);
+ }
+
+ function identifiedTouch(touchList, id) {
+ var i, l;
+
+ if (touchList.identifiedTouch) {
+ return touchList.identifiedTouch(id);
+ }
+
+ // touchList.identifiedTouch() does not exist in
+ // webkit yet⦠we must do the search ourselves...
+
+ i = -1;
+ l = touchList.length;
+
+ while (++i < l) {
+ if (touchList[i].identifier === id) {
+ return touchList[i];
+ }
+ }
+ }
+
+ function changedTouch(e, data) {
+ var touch = identifiedTouch(e.changedTouches, data.identifier);
+
+ // This isn't the touch you're looking for.
+ if (!touch) { return; }
+
+ // Chrome Android (at least) includes touches that have not
+ // changed in e.changedTouches. That's a bit annoying. Check
+ // that this touch has changed.
+ if (touch.pageX === data.pageX && touch.pageY === data.pageY) { return; }
+
+ return touch;
+ }
+
+
+ // Handlers that decide when the first movestart is triggered
+
+ function mousedown(e){
+ // Ignore non-primary buttons
+ if (!isPrimaryButton(e)) { return; }
+
+ // Ignore form and interactive elements
+ if (isIgnoreTag(e)) { return; }
+
+ on(document, mouseevents.move, mousemove, e);
+ on(document, mouseevents.cancel, mouseend, e);
+ }
+
+ function mousemove(e, data){
+ checkThreshold(e, data, e, removeMouse);
+ }
+
+ function mouseend(e, data) {
+ removeMouse();
+ }
+
+ function removeMouse() {
+ off(document, mouseevents.move, mousemove);
+ off(document, mouseevents.cancel, mouseend);
+ }
+
+ function touchstart(e) {
+ // Don't get in the way of interaction with form elements
+ if (ignoreTags[e.target.tagName.toLowerCase()]) { return; }
+
+ var touch = e.changedTouches[0];
+
+ // iOS live updates the touch objects whereas Android gives us copies.
+ // That means we can't trust the touchstart object to stay the same,
+ // so we must copy the data. This object acts as a template for
+ // movestart, move and moveend event objects.
+ var data = {
+ target: touch.target,
+ pageX: touch.pageX,
+ pageY: touch.pageY,
+ identifier: touch.identifier,
+
+ // The only way to make handlers individually unbindable is by
+ // making them unique.
+ touchmove: function(e, data) { touchmove(e, data); },
+ touchend: function(e, data) { touchend(e, data); }
+ };
+
+ on(document, touchevents.move, data.touchmove, data);
+ on(document, touchevents.cancel, data.touchend, data);
+ }
+
+ function touchmove(e, data) {
+ var touch = changedTouch(e, data);
+ if (!touch) { return; }
+ checkThreshold(e, data, touch, removeTouch);
+ }
+
+ function touchend(e, data) {
+ var touch = identifiedTouch(e.changedTouches, data.identifier);
+ if (!touch) { return; }
+ removeTouch(data);
+ }
+
+ function removeTouch(data) {
+ off(document, touchevents.move, data.touchmove);
+ off(document, touchevents.cancel, data.touchend);
+ }
+
+ function checkThreshold(e, data, touch, fn) {
+ var distX = touch.pageX - data.pageX;
+ var distY = touch.pageY - data.pageY;
+
+ // Do nothing if the threshold has not been crossed.
+ if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; }
+
+ triggerStart(e, data, touch, distX, distY, fn);
+ }
+
+ function triggerStart(e, data, touch, distX, distY, fn) {
+ var touches = e.targetTouches;
+ var time = e.timeStamp - data.timeStamp;
+
+ // Create a movestart object with some special properties that
+ // are passed only to the movestart handlers.
+ var template = {
+ altKey: e.altKey,
+ ctrlKey: e.ctrlKey,
+ shiftKey: e.shiftKey,
+ startX: data.pageX,
+ startY: data.pageY,
+ distX: distX,
+ distY: distY,
+ deltaX: distX,
+ deltaY: distY,
+ pageX: touch.pageX,
+ pageY: touch.pageY,
+ velocityX: distX / time,
+ velocityY: distY / time,
+ identifier: data.identifier,
+ targetTouches: touches,
+ finger: touches ? touches.length : 1,
+ enableMove: function() {
+ this.moveEnabled = true;
+ this.enableMove = noop;
+ e.preventDefault();
+ }
+ };
+
+ // Trigger the movestart event.
+ trigger(data.target, 'movestart', template);
+
+ // Unbind handlers that tracked the touch or mouse up till now.
+ fn(data);
+ }
+
+
+ // Handlers that control what happens following a movestart
+
+ function activeMousemove(e, data) {
+ var timer = data.timer;
+
+ data.touch = e;
+ data.timeStamp = e.timeStamp;
+ timer.kick();
+ }
+
+ function activeMouseend(e, data) {
+ var target = data.target;
+ var event = data.event;
+ var timer = data.timer;
+
+ removeActiveMouse();
+
+ endEvent(target, event, timer, function() {
+ // Unbind the click suppressor, waiting until after mouseup
+ // has been handled.
+ setTimeout(function(){
+ off(target, 'click', preventDefault);
+ }, 0);
+ });
+ }
+
+ function removeActiveMouse() {
+ off(document, mouseevents.move, activeMousemove);
+ off(document, mouseevents.end, activeMouseend);
+ }
+
+ function activeTouchmove(e, data) {
+ var event = data.event;
+ var timer = data.timer;
+ var touch = changedTouch(e, event);
+
+ if (!touch) { return; }
+
+ // Stop the interface from gesturing
+ e.preventDefault();
+
+ event.targetTouches = e.targetTouches;
+ data.touch = touch;
+ data.timeStamp = e.timeStamp;
+
+ timer.kick();
+ }
+
+ function activeTouchend(e, data) {
+ var target = data.target;
+ var event = data.event;
+ var timer = data.timer;
+ var touch = identifiedTouch(e.changedTouches, event.identifier);
+
+ // This isn't the touch you're looking for.
+ if (!touch) { return; }
+
+ removeActiveTouch(data);
+ endEvent(target, event, timer);
+ }
+
+ function removeActiveTouch(data) {
+ off(document, touchevents.move, data.activeTouchmove);
+ off(document, touchevents.end, data.activeTouchend);
+ }
+
+
+ // Logic for triggering move and moveend events
+
+ function updateEvent(event, touch, timeStamp) {
+ var time = timeStamp - event.timeStamp;
+
+ event.distX = touch.pageX - event.startX;
+ event.distY = touch.pageY - event.startY;
+ event.deltaX = touch.pageX - event.pageX;
+ event.deltaY = touch.pageY - event.pageY;
+
+ // Average the velocity of the last few events using a decay
+ // curve to even out spurious jumps in values.
+ event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
+ event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
+ event.pageX = touch.pageX;
+ event.pageY = touch.pageY;
+ }
+
+ function endEvent(target, event, timer, fn) {
+ timer.end(function(){
+ trigger(target, 'moveend', event);
+ return fn && fn();
+ });
+ }
+
+
+ // Set up the DOM
+
+ function movestart(e) {
+ if (e.defaultPrevented) { return; }
+ if (!e.moveEnabled) { return; }
+
+ var event = {
+ startX: e.startX,
+ startY: e.startY,
+ pageX: e.pageX,
+ pageY: e.pageY,
+ distX: e.distX,
+ distY: e.distY,
+ deltaX: e.deltaX,
+ deltaY: e.deltaY,
+ velocityX: e.velocityX,
+ velocityY: e.velocityY,
+ identifier: e.identifier,
+ targetTouches: e.targetTouches,
+ finger: e.finger
+ };
+
+ var data = {
+ target: e.target,
+ event: event,
+ timer: new Timer(update),
+ touch: undefined,
+ timeStamp: e.timeStamp
+ };
+
+ function update(time) {
+ updateEvent(event, data.touch, data.timeStamp);
+ trigger(data.target, 'move', event);
+ }
+
+ if (e.identifier === undefined) {
+ // We're dealing with a mouse event.
+ // Stop clicks from propagating during a move
+ on(e.target, 'click', preventDefault);
+ on(document, mouseevents.move, activeMousemove, data);
+ on(document, mouseevents.end, activeMouseend, data);
+ }
+ else {
+ // In order to unbind correct handlers they have to be unique
+ data.activeTouchmove = function(e, data) { activeTouchmove(e, data); };
+ data.activeTouchend = function(e, data) { activeTouchend(e, data); };
+
+ // We're dealing with a touch.
+ on(document, touchevents.move, data.activeTouchmove, data);
+ on(document, touchevents.end, data.activeTouchend, data);
+ }
+ }
+
+ on(document, 'mousedown', mousedown);
+ on(document, 'touchstart', touchstart);
+ on(document, 'movestart', movestart);
+
+
+ // jQuery special events
+ //
+ // jQuery event objects are copies of DOM event objects. They need
+ // a little help copying the move properties across.
+
+ if (!window.jQuery) { return; }
+
+ var properties = ("startX startY pageX pageY distX distY deltaX deltaY velocityX velocityY").split(' ');
+
+ function enableMove1(e) { e.enableMove(); }
+ function enableMove2(e) { e.enableMove(); }
+ function enableMove3(e) { e.enableMove(); }
+
+ function add(handleObj) {
+ var handler = handleObj.handler;
+
+ handleObj.handler = function(e) {
+ // Copy move properties across from originalEvent
+ var i = properties.length;
+ var property;
+
+ while(i--) {
+ property = properties[i];
+ e[property] = e.originalEvent[property];
+ }
+
+ handler.apply(this, arguments);
+ };
+ }
+
+ jQuery.event.special.movestart = {
+ setup: function() {
+ // Movestart must be enabled to allow other move events
+ on(this, 'movestart', enableMove1);
+
+ // Do listen to DOM events
+ return false;
+ },
+
+ teardown: function() {
+ off(this, 'movestart', enableMove1);
+ return false;
+ },
+
+ add: add
+ };
+
+ jQuery.event.special.move = {
+ setup: function() {
+ on(this, 'movestart', enableMove2);
+ return false;
+ },
+
+ teardown: function() {
+ off(this, 'movestart', enableMove2);
+ return false;
+ },
+
+ add: add
+ };
+
+ jQuery.event.special.moveend = {
+ setup: function() {
+ on(this, 'movestart', enableMove3);
+ return false;
+ },
+
+ teardown: function() {
+ off(this, 'movestart', enableMove3);
+ return false;
+ },
+
+ add: add
+ };
+});
diff --git a/wp-content/plugins/imagify/assets/js/jquery.event.move.min.js b/wp-content/plugins/imagify/assets/js/jquery.event.move.min.js
new file mode 100644
index 00000000..fefd7eb2
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/jquery.event.move.min.js
@@ -0,0 +1 @@
+!function(a){"function"==typeof define&&define.amd?define([],a):"undefined"!=typeof module&&null!==module&&module.exports?module.exports=a:a()}(function(){function a(a){return new CustomEvent(a,Q)}function b(a){return a[R]||(a[R]={})}function c(a,c,d,e,f){function g(a){d(a,e)}c=c.split(P);for(var h,i,j=b(a),k=c.length;k--;)i=c[k],h=j[i]||(j[i]=[]),h.push([d,g]),a.addEventListener(i,g)}function d(a,c,d,e){c=c.split(P);var f,g,h,i=b(a),j=c.length;if(i)for(;j--;)if(f=c[j],g=i[f])for(h=g.length;h--;)g[h][0]===d&&(a.removeEventListener(f,g[h][1]),g.splice(h,1))}function e(b,c,d){var e=a(c);d&&J(e,d),b.dispatchEvent(e)}function f(a){function b(a){d?(c(),L(b),e=!0,d=!1):e=!1}var c=a,d=!1,e=!1;this.kick=function(a){d=!0,e||b()},this.end=function(a){var b=c;a&&(e?(c=d?function(){b(),a()}:a,d=!0):a())}}function g(){}function h(a){a.preventDefault()}function i(a){return!!M[a.target.tagName.toLowerCase()]}function j(a){return 1===a.which&&!a.ctrlKey&&!a.altKey}function k(a,b){var c,d;if(a.identifiedTouch)return a.identifiedTouch(b);for(c=-1,d=a.length;++c' );
+
+ $container.children( '.twentytwenty-overlay, .twentytwenty-handle' ).remove();
+ $container.append( '
' );
+ $container.append( '
' );
+
+ $slider = $container.find( '.twentytwenty-handle' );
+
+ $slider.append( ' ' );
+ $slider.append( ' ' );
+ $container.addClass( 'twentytwenty-container' );
+ $beforeImg.addClass( 'twentytwenty-before' );
+ $afterImg.addClass( 'twentytwenty-after' );
+
+ $overlay = $container.find( '.twentytwenty-overlay' );
+
+ $overlay.append( '' + options.labelBefore + '
' );
+ $overlay.append( '' + options.labelAfter + '
' );
+
+ $( w ).on( 'resize.twentytwenty', function() {
+ adjustSlider( sliderPct );
+ } );
+
+ $slider.on( 'movestart', function( e ) {
+ if ( 'vertical' !== sliderOrientation && ( ( e.distX > e.distY && e.distX < -e.distY ) || ( e.distX < e.distY && e.distX > -e.distY ) ) ) {
+ e.preventDefault();
+ } else if ( 'vertical' === sliderOrientation && ( ( e.distX < e.distY && e.distX < -e.distY ) || ( e.distX > e.distY && e.distX > -e.distY ) ) ) {
+ e.preventDefault();
+ }
+
+ $container.addClass( 'active' );
+
+ offsetX = $container.offset().left;
+ offsetY = $container.offset().top;
+ imgWidth = $beforeImg.width();
+ imgHeight = $beforeImg.height();
+ } );
+
+ $slider.on( 'moveend', function() {
+ $container.removeClass( 'active' );
+ } );
+
+ $slider.on( 'move', function( e ) {
+ if ( $container.hasClass('active') ) {
+ sliderPct = 'vertical' === sliderOrientation ? ( e.pageY - offsetY ) / imgHeight : ( e.pageX - offsetX ) / imgWidth;
+
+ if ( sliderPct < 0 ) {
+ sliderPct = 0;
+ } else if ( sliderPct > 1 ) {
+ sliderPct = 1;
+ }
+
+ adjustSlider( sliderPct );
+ }
+ } );
+
+ $container.find( 'img' ).on( 'mousedown', function( e ) {
+ e.preventDefault();
+ } );
+
+ $( w ).trigger( 'resize.twentytwenty' );
+ } );
+ };
+
+} )(jQuery, document, window);
+
+/**
+ * Twentytwenty Imagify Init
+ */
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ /*
+ * Mini chart
+ *
+ * @param {element} canvas
+ */
+ var drawMeAChart = function ( canvas ) {
+ canvas.each( function() {
+ var value = parseInt( $( this ).closest( '.imagify-chart' ).next( '.imagify-chart-value' ).text(), 10 );
+
+ new w.imagify.Chart( this, { // eslint-disable-line no-new
+ type: 'doughnut',
+ data: {
+ datasets: [{
+ data: [ value, 100 - value ],
+ backgroundColor: [ '#00B3D3', '#D8D8D8' ],
+ borderColor: '#2A2E3C',
+ borderWidth: 1
+ }]
+ },
+ options: {
+ legend: {
+ display: false
+ },
+ events: [],
+ animation: {
+ easing: 'easeOutBounce'
+ },
+ tooltips: {
+ enabled: false
+ },
+ responsive: false,
+ cutoutPercentage: 60
+ }
+ } );
+ } );
+ },
+ /**
+ * Dynamic modal
+ *
+ * @param {object} Parameters to build modal with datas
+ */
+ imagifyTwentyModal = function( options ) {
+ var defaults = {
+ width: 0, //px
+ height: 0, //px
+ originalUrl: '', //url
+ optimizedUrl: '', //url
+ originalSize: 0, //mb
+ optimizedSize: 0, // mb
+ saving: 0, //percent
+ modalAppendTo: $( 'body' ), // jQuery element
+ trigger: $( '[data-target="imagify-visual-comparison"]' ), // jQuery element (button, link) with data-target="modalId"
+ modalId: 'imagify-visual-comparison', // should be dynamic if multiple modals
+ openModal: false
+ },
+ settings = $.extend( {}, defaults, options ),
+ modalHtml;
+
+ if ( 0 === settings.width || 0 === settings.height || '' === settings.originalUrl || '' === settings.optimizedUrl || 0 === settings.originalSize || 0 === settings.optimizedSize || 0 === settings.saving ) {
+ return 'error';
+ }
+
+ // create modal box
+ modalHtml = '';
+ /* eslint-disable indent */
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '' + imagifyTTT.labels.filesize + ' ';
+ modalHtml += '' + settings.originalSize + ' ';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '' + imagifyTTT.labels.filesize + ' ';
+ modalHtml += '' + settings.optimizedSize + ' ';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '' + imagifyTTT.labels.saving + ' ';
+ modalHtml += '' + settings.saving + ' % ';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
';
+ modalHtml += '
' + imagifyTTT.labels.close + ' ';
+ modalHtml += '
';
+ /* eslint-enable indent */
+ modalHtml += '
';
+
+ settings.modalAppendTo.append( modalHtml );
+
+ settings.trigger.on( 'click.imagify', function( e ) {
+ var $modal = $( $( this ).data( 'target' ) ),
+ imgsLoaded = 0,
+ $tt, checkLoad;
+
+ e.preventDefault();
+
+ if ( settings.openModal ) {
+ w.imagify.openModal( $( this ) );
+ }
+
+ $modal.find( '.imagify-modal-content' ).css( {
+ 'width': ( $( w ).outerWidth() * 0.85 ) + 'px',
+ 'max-width': settings.width
+ } );
+
+ // Load before img.
+ $modal.find( '.imagify-img-before' ).on( 'load', function() {
+ imgsLoaded++;
+ } ).attr( 'src', settings.originalUrl );
+
+ // Load after img.
+ $modal.find( '.imagify-img-after' ).on( 'load', function() {
+ imgsLoaded++;
+ } ).attr( 'src', settings.optimizedUrl + ( settings.optimizedUrl.indexOf( '?' ) > 0 ? '&' : '?' ) + 'v=' + Date.now() );
+
+ $tt = $modal.find( '.twentytwenty-container' );
+ checkLoad = setInterval( function() {
+ if ( 2 !== imgsLoaded ) {
+ return;
+ }
+
+ $tt.twentytwenty( {
+ handlePosition: 0.3,
+ orientation: 'horizontal',
+ labelBefore: imagifyTTT.labels.originalL,
+ labelAfter: imagifyTTT.labels.optimizedL
+ }, function() {
+ var windowH = $( w ).height(),
+ ttH = $modal.find( '.twentytwenty-container' ).height(),
+ ttTop = $modal.find( '.twentytwenty-wrapper' ).position().top,
+ $handle, $labels, $datas, datasH, handlePos, labelsPos;
+
+ if ( ! $tt.closest( '.imagify-modal-content' ).hasClass( 'loaded' ) ) {
+ $tt.closest( '.imagify-modal-content' ).removeClass( 'loading' ).addClass( 'loaded' );
+ drawMeAChart( $modal.find( '.imagify-level-optimized .imagify-chart canvas' ) );
+ }
+
+ // Check if image height is to big.
+ if ( windowH < ttH && ! $modal.hasClass( 'modal-is-too-high' ) ) {
+ $modal.addClass( 'modal-is-too-high' );
+
+ $handle = $modal.find( '.twentytwenty-handle' );
+ $labels = $modal.find( '.twentytwenty-label-content' );
+ $datas = $modal.find( '.imagify-comparison-levels' );
+ datasH = $datas.outerHeight();
+ handlePos = ( windowH - ttTop - $handle.height() ) / 2;
+ labelsPos = ( windowH - ttTop * 3 - datasH );
+
+ $handle.css( {
+ top: handlePos
+ } );
+ $labels.css( {
+ top: labelsPos,
+ bottom: 'auto'
+ } );
+ $modal.find( '.twentytwenty-wrapper' ).css( {
+ paddingBottom: datasH
+ } );
+ $modal.find( '.imagify-modal-content' ).on( 'scroll.imagify', function() {
+ var scrollTop = $( this ).scrollTop();
+
+ $handle.css( {
+ top: handlePos + scrollTop
+ } );
+ $labels.css( {
+ top: labelsPos + scrollTop
+ } );
+ $datas.css( {
+ bottom: -scrollTop
+ } );
+ } );
+ }
+ } );
+
+ clearInterval( checkLoad );
+ checkLoad = null;
+ return 'done';
+ }, 75 );
+ } );
+ }; // imagifyTwentyModal( options );
+
+
+ /**
+ * The complexe visual comparison
+ */
+ $( '.imagify-visual-comparison-btn' ).on( 'click', function() {
+ var $tt, imgsLoaded, loader,
+ labelOriginal, labelNormal, labelAggressive, labelUltra,
+ originalLabel, originalAlt, originalSrc, originalDim,
+ normalAlt, normalSrc, normalDim,
+ aggressiveAlt, aggressiveSrc, aggressiveDim,
+ ultraLabel, ultraAlt, ultraSrc, ultraDim,
+ ttBeforeButtons, ttAfterButtons, image50, twentyMe;
+
+ if ( $( '.twentytwenty-wrapper' ).length === 1 ) {
+ return;
+ }
+
+ $( $( this ).data( 'target' ) ).find( '.imagify-modal-content' ).css( 'width', ( $( w ).outerWidth() * 0.95 ) + 'px' );
+
+ if ( $( '.twentytwenty-container' ).length > 0 && $( w ).outerWidth() <= 800 ) {
+ return;
+ }
+
+ $tt = $( '.twentytwenty-container' );
+ imgsLoaded = 0;
+ loader = $tt.data( 'loader' );
+ labelOriginal = $tt.data( 'label-original' );
+ labelNormal = $tt.data( 'label-normal' );
+ labelAggressive = $tt.data( 'label-aggressive' );
+ labelUltra = $tt.data( 'label-ultra' );
+
+ originalLabel = $tt.data( 'original-label' ).replace( /\*\*/, '' ).replace( /\*\*/, ' ' );
+ originalAlt = $tt.data( 'original-alt' );
+ originalSrc = $tt.data( 'original-img' );
+ originalDim = $tt.data( 'original-dim' ).split( 'x' );
+
+ normalAlt = $tt.data( 'normal-alt' );
+ normalSrc = $tt.data( 'normal-img' );
+ normalDim = $tt.data( 'normal-dim' ).split( 'x' );
+
+ aggressiveAlt = $tt.data( 'aggressive-alt' );
+ aggressiveSrc = $tt.data( 'aggressive-img' );
+ aggressiveDim = $tt.data( 'aggressive-dim' ).split( 'x' );
+
+ ultraLabel = $tt.data( 'ultra-label' ).replace( /\*\*/, '' ).replace( /\*\*/, ' ' );
+ ultraAlt = $tt.data( 'ultra-alt' );
+ ultraSrc = $tt.data( 'ultra-img' );
+ ultraDim = $tt.data( 'ultra-dim' ).split( 'x' );
+
+ ttBeforeButtons = '';
+ /* eslint-disable indent */
+ ttBeforeButtons += '' + labelOriginal + ' ';
+ ttBeforeButtons += '' + labelNormal + ' ';
+ ttBeforeButtons += '' + labelAggressive + ' ';
+ /* eslint-enable indent */
+ ttBeforeButtons += ' ';
+ ttAfterButtons = '';
+ /* eslint-disable indent */
+ ttAfterButtons += '' + labelNormal + ' ';
+ ttAfterButtons += '' + labelAggressive + ' ';
+ ttAfterButtons += '' + labelUltra + ' ';
+ /* eslint-enable indent */
+ ttAfterButtons += ' ';
+
+ // Loader.
+ $tt.before( ' ' );
+
+ // Should be more locally integrated...
+ $( '.twentytwenty-left-buttons' ).append( ttBeforeButtons );
+ $( '.twentytwenty-right-buttons' ).append( ttAfterButtons );
+
+ image50 = ' ';
+ image50 += ' ';
+ image50 += ' ';
+ image50 += ' ';
+ // Add switchers button only if needed.
+ // Should be more locally integrated...
+ image50 += $( '.twentytwenty-left-buttons' ).lenght ? ttBeforeButtons + ttAfterButtons : '';
+
+ // Add images to 50/50 area.
+ $tt.closest( '.imagify-modal-content' ).addClass( 'loading' ).find( '.twentytwenty-container' ).append( image50 );
+
+ // Load image original.
+ $( '.img-original' ).on( 'load', function() {
+ imgsLoaded++;
+ } ).attr( 'src', originalSrc );
+
+ // Load image normal.
+ $( '.img-normal' ).on( 'load', function() {
+ imgsLoaded++;
+ } ).attr( 'src', normalSrc );
+
+ // Load image aggressive.
+ $( '.img-aggressive' ).on( 'load', function() {
+ imgsLoaded++;
+ } ).attr( 'src', aggressiveSrc );
+
+ // Load image ultra.
+ $( '.img-ultra' ).on( 'load', function() {
+ imgsLoaded++;
+ } ).attr( 'src', ultraSrc );
+
+ twentyMe = setInterval( function() {
+ if ( 4 !== imgsLoaded ) {
+ return;
+ }
+
+ $tt.twentytwenty({
+ handlePosition: 0.6,
+ orientation: 'horizontal',
+ labelBefore: originalLabel,
+ labelAfter: ultraLabel
+ }, function() {
+ // Fires on initialisation & each time the handle is moving.
+ if ( ! $tt.closest( '.imagify-modal-content' ).hasClass( 'loaded' ) ) {
+ $tt.closest( '.imagify-modal-content' ).removeClass( 'loading' ).addClass( 'loaded' );
+ drawMeAChart( $( '.imagify-level-ultra .imagify-chart canvas' ) );
+ }
+ } );
+
+ clearInterval( twentyMe );
+ twentyMe = null;
+ }, 75);
+
+ // On click on button choices.
+ $( '.imagify-comparison-title' ).on( 'click', '.twentytwenty-duo-buttons button:not(.selected)', function( e ) {
+ var $this = $( this ),
+ $container = $this.closest( '.imagify-comparison-title' ).nextAll( '.twentytwenty-wrapper' ).find( '.twentytwenty-container' ),
+ side = $this.closest( '.twentytwenty-duo-buttons' ).hasClass( 'twentytwenty-duo-left' ) ? 'left' : 'right',
+ $otherSide = 'left' === side ? $this.closest( '.imagify-comparison-title' ).find( '.twentytwenty-duo-right' ) : $this.closest( '.imagify-comparison-title' ).find( '.twentytwenty-duo-left' ),
+ $duo = $this.closest( '.twentytwenty-duo-buttons' ).find( 'button' ),
+ $imgBefore = $container.find( '.twentytwenty-before' ),
+ $imgAfter = $container.find( '.twentytwenty-after' ),
+ image = $this.data( 'img' ),
+ clipStyles;
+
+ e.stopPropagation();
+ e.preventDefault();
+
+ // Button coloration.
+ $duo.removeClass( 'selected' );
+ $this.addClass( 'selected' );
+
+ // Other side action (to not compare same images).
+ if ( $otherSide.find( '.selected' ).data( 'img' ) === image ) {
+ $otherSide.find( 'button:not(.selected)' ).eq( 0 ).trigger( 'click' );
+ }
+
+ // Left buttons.
+ if ( 'left' === side ) {
+ clipStyles = $imgBefore.css( 'clip' );
+ $imgBefore.attr( 'style', '' );
+ $imgBefore.removeClass( 'twentytwenty-before' );
+ $container.find( '.img-' + image ).addClass( 'twentytwenty-before' ).css( 'clip', clipStyles );
+ $( '.twentytwenty-before-label .twentytwenty-label-content' ).text( $container.data( image + '-label' ) );
+ $( '.imagify-c-level.go-left' ).attr( 'aria-hidden', 'true' ).removeClass( 'go-left go-right' );
+ $( '.imagify-level-' + image ).attr( 'aria-hidden', 'false' ).addClass( 'go-left' );
+ }
+
+ // Right buttons.
+ if ( 'right' === side ) {
+ $imgAfter.removeClass( 'twentytwenty-after' );
+ $container.find( '.img-' + image ).addClass( 'twentytwenty-after' );
+ $( '.twentytwenty-after-label .twentytwenty-label-content' ).text( $container.data( image + '-label' ) );
+ $( '.imagify-c-level.go-right' ).attr( 'aria-hidden', 'true' ).removeClass( 'go-left go-right' );
+ $( '.imagify-level-' + image ).attr( 'aria-hidden', 'false' ).addClass( 'go-right' );
+ }
+
+ drawMeAChart( $( '.imagify-level-' + image + ' .imagify-chart canvas' ) );
+ } );
+ } );
+
+
+ /**
+ * Imagify comparison inside Media post edition.
+ */
+ if ( imagifyTTT.imageWidth && $( '.post-php .wp_attachment_image .thumbnail' ).length > 0 ) {
+
+ var $oriParent = $( '.post-php .wp_attachment_image' ),
+ oriSource = { src: $( '#imagify-full-original' ).val(), size: $( '#imagify-full-original-size' ).val() },
+ $optimizeBtn = $( '#misc-publishing-actions' ).find( '.misc-pub-imagify .button-primary' ),
+ filesize, saving;
+
+ imagifyTTT.widthLimit = parseInt( imagifyTTT.widthLimit, 10 );
+
+ // If shown image > 360, use twentytwenty.
+ if ( imagifyTTT.imageWidth > imagifyTTT.widthLimit && oriSource.src ) {
+
+ filesize = $( '.misc-pub-filesize strong' ).text();
+ saving = $( '.imagify-data-item .imagify-chart-value' ).text();
+
+ // Create button to trigger.
+ $( '[id^="imgedit-open-btn-"]' ).before( '' + imagifyTTT.labels.compare + ' ' );
+
+ // Modal and trigger event creation.
+ imagifyTwentyModal( {
+ width: parseInt( imagifyTTT.imageWidth, 10 ),
+ height: parseInt( imagifyTTT.imageHeight, 10 ),
+ originalUrl: oriSource.src,
+ optimizedUrl: imagifyTTT.imageSrc,
+ originalSize: oriSource.size,
+ optimizedSize: filesize,
+ saving: saving,
+ modalAppendTo: $oriParent,
+ trigger: $( '#imagify-start-comparison' ),
+ modalId: 'imagify-visual-comparison'
+ } );
+ }
+ // Else put images next to next.
+ else if ( imagifyTTT.imageWidth < imagifyTTT.widthLimit && oriSource.src ) {
+ // TODO
+ }
+ // If image has no backup.
+ else if ( $( '#imagify-full-original' ).length > 0 && '' === oriSource.src ) {
+ // do nothing ?
+ }
+ // In case image is not optimized.
+ else {
+ // If is not in optimizing process, propose the Optimize button trigger.
+ if ( $( '#misc-publishing-actions' ).find( '.misc-pub-imagify .button-primary' ).length === 1 ) {
+ $( '[id^="imgedit-open-btn-"]' ).before( '' + imagifyTTT.labels.optimize + ' ' );
+
+ $( '#imagify-optimize-trigger' ).on( 'click', function() {
+ $( this ).prev( '.spinner' ).removeClass( 'imagify-hidden' ).addClass( 'is-active' );
+ } );
+ }
+ }
+
+ }
+
+ /**
+ * Images comparison in attachments list page (upload.php).
+ */
+ if ( $( '.upload-php .imagify-compare-images' ).length > 0 ) {
+
+ $( '.imagify-compare-images' ).each( function() {
+ var $this = $( this ),
+ id = $this.data( 'id' ),
+ $datas = $this.closest( '#post-' + id ).find( '.column-imagify_optimized_file' );
+
+ // Modal and trigger event creation.
+ imagifyTwentyModal( {
+ width: parseInt( $this.data( 'full-width' ), 10 ),
+ height: parseInt( $this.data( 'full-height' ), 10 ),
+ originalUrl: $this.data( 'backup-src' ),
+ optimizedUrl: $this.data( 'full-src' ),
+ originalSize: $datas.find( '.original' ).text(),
+ optimizedSize: $datas.find( '.imagify-data-item .big' ).text(),
+ saving: $datas.find( '.imagify-chart-value' ).text(),
+ modalAppendTo: $this.closest( '.column-primary' ),
+ trigger: $this,
+ modalId: 'imagify-comparison-' + id
+ } );
+ } );
+ }
+
+ /**
+ * Images Comparison in Grid View modal.
+ */
+ if ( $( '.upload-php' ).length > 0 ) {
+
+ var getVar = function( param ) {
+ var vars = {};
+
+ w.location.href.replace(
+ /[?&]+([^=&]+)=?([^&]*)?/gi,
+ function( m, key, value ) {
+ vars[ key ] = undefined !== value ? value : '';
+ }
+ );
+
+ if ( param ) {
+ return vars[ param ] ? vars[ param ] : null;
+ }
+ return vars;
+ },
+ imagifyContentInModal = function() {
+ var tempTimer = setInterval( function() {
+ var $datas, originalSrc, $actions;
+
+ if ( ! $( '.media-modal .imagify-datas-details' ).length ) {
+ return;
+ }
+
+ originalSrc = $( '#imagify-original-src' ).val();
+
+ if ( originalSrc ) {
+ // Trigger creation.
+ $actions = $( '.media-frame-content .attachment-actions' );
+
+ $actions.find( '#imagify-media-frame-comparison-btn' ).remove();
+ $actions.prepend( '' + imagifyTTT.labels.compare + ' ' );
+
+ // Get datas.
+ $datas = $( '.media-frame-content .compat-field-imagify' );
+
+ // Modal and trigger event creation.
+ imagifyTwentyModal( {
+ width: parseInt( $( '#imagify-full-width' ).val(), 10 ),
+ height: parseInt( $( '#imagify-full-height' ).val(), 10 ),
+ originalUrl: originalSrc,
+ optimizedUrl: $( '#imagify-full-src' ).val(),
+ originalSize: $( '#imagify-original-size' ).val(),
+ optimizedSize: $datas.find( '.imagify-data-item .big' ).text(),
+ saving: $datas.find( '.imagify-chart-value' ).text(),
+ modalAppendTo: $( '.media-frame-content .thumbnail-image' ),
+ trigger: $( '#imagify-media-frame-comparison-btn' ),
+ modalId: 'imagify-comparison-modal',
+ openModal: true
+ } );
+ }
+
+ clearInterval( tempTimer );
+ tempTimer = null;
+ }, 20 );
+ };
+
+ // If attachment is clicked, or the "Previous" and "Next" buttons, build the modal inside the modal.
+ $( '.upload-php' ).on( 'click', '.media-frame.mode-grid .attachment, .edit-media-header .left, .edit-media-header .right', function() {
+ imagifyContentInModal();
+ } );
+
+ // If attachment is mentionned in URL, build the modal inside the modal.
+ if ( getVar( 'item' ) ) {
+ imagifyContentInModal();
+ }
+ }
+
+ /**
+ * Images comparison in custom folders list page.
+ */
+ if ( $( '#imagify-files-list-form' ).length > 0 ) {
+
+ var buildComparisonModal = function( $buttons ) {
+ $buttons.each( function() {
+ var $this = $( this ),
+ id = $this.data( 'id' ),
+ $datas = $this.closest( 'tr' ).find( '.column-optimization .imagify-data-item' );
+
+ $( '#imagify-comparison-' + id ).remove();
+
+ // Modal and trigger event creation.
+ imagifyTwentyModal( {
+ width: parseInt( $this.data( 'full-width' ), 10 ),
+ height: parseInt( $this.data( 'full-height' ), 10 ),
+ originalUrl: $this.data( 'backup-src' ),
+ optimizedUrl: $this.data( 'full-src' ),
+ originalSize: $datas.find( '.original' ).text(),
+ optimizedSize: $datas.find( '.optimized' ).text(),
+ saving: $datas.find( '.imagify-chart-value' ).text(),
+ modalAppendTo: $this.closest( '.column-primary' ),
+ trigger: $this,
+ modalId: 'imagify-comparison-' + id
+ } );
+ } );
+ };
+
+ /**
+ * Update the comparison tool window when a file row is updated via ajax, and the ones already printed.
+ */
+ $( w ).on( 'comparisonprinted.imagify', function( e, id ) {
+ var $buttons;
+
+ id = id || 0;
+
+ if ( id ) {
+ $buttons = $( '#imagify-files-list-form' ).find( '.imagify-compare-images[data-id="' + id + '"]' );
+ } else {
+ $buttons = $( '#imagify-files-list-form' ).find( '.imagify-compare-images' );
+ }
+
+ if ( $buttons.length ) {
+ buildComparisonModal( $buttons );
+ }
+ } )
+ .trigger( 'comparisonprinted.imagify' );
+ }
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/jquery.twentytwenty.min.js b/wp-content/plugins/imagify/assets/js/jquery.twentytwenty.min.js
new file mode 100644
index 00000000..8cf47e29
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/jquery.twentytwenty.min.js
@@ -0,0 +1 @@
+!function(a,b,c,d){a.fn.twentytwenty=function(b,d){return b=a.extend({handlePosition:.5,orientation:"horizontal",labelBefore:"Before",labelAfter:"After"},b),this.each(function(){var e,f,g=b.handlePosition,h=a(this),i=b.orientation,j="vertical"===i?"down":"left",k="vertical"===i?"up":"right",l=h.find("img:first"),m=h.find("img:last"),n=0,o=0,p=0,q=0,r=function(a){var b=parseInt(l.width(),10),c=parseInt(l.height(),10);return b&&c||(b=parseInt(l.attr("width"),10),c=parseInt(l.attr("height"),10)),{w:b+"px",h:c+"px",cw:a*b+"px",ch:a*c+"px"}},s=function(a){var b=h.find(".twentytwenty-before");"vertical"===i?b.css("clip","rect(0,"+a.w+","+a.ch+",0)"):b.css("clip","rect(0,"+a.cw+","+a.h+",0)"),h.css("height",a.h),"function"==typeof d&&d()},t=function(a){var b=r(a);"vertical"===i?e.css("top",b.ch):e.css("left",b.cw),s(b)};h.parent(".twentytwenty-wrapper").length&&h.unwrap(),h.wrap('
'),h.children(".twentytwenty-overlay, .twentytwenty-handle").remove(),h.append('
'),h.append('
'),e=h.find(".twentytwenty-handle"),e.append(' '),e.append(' '),h.addClass("twentytwenty-container"),l.addClass("twentytwenty-before"),m.addClass("twentytwenty-after"),f=h.find(".twentytwenty-overlay"),f.append(''+b.labelBefore+"
"),f.append(''+b.labelAfter+"
"),a(c).on("resize.twentytwenty",function(){t(g)}),e.on("movestart",function(a){"vertical"!==i&&(a.distX>a.distY&&a.distX<-a.distY||a.distX-a.distY)?a.preventDefault():"vertical"===i&&(a.distXa.distY&&a.distX>-a.distY)&&a.preventDefault(),h.addClass("active"),n=h.offset().left,o=h.offset().top,p=l.width(),q=l.height()}),e.on("moveend",function(){h.removeClass("active")}),e.on("move",function(a){h.hasClass("active")&&(g="vertical"===i?(a.pageY-o)/q:(a.pageX-n)/p,g<0?g=0:g>1&&(g=1),t(g))}),h.find("img").on("mousedown",function(a){a.preventDefault()}),a(c).trigger("resize.twentytwenty")})}}(jQuery,document,window),function(a,b,c,d){var e=function(b){b.each(function(){var b=parseInt(a(this).closest(".imagify-chart").next(".imagify-chart-value").text(),10);new c.imagify.Chart(this,{type:"doughnut",data:{datasets:[{data:[b,100-b],backgroundColor:["#00B3D3","#D8D8D8"],borderColor:"#2A2E3C",borderWidth:1}]},options:{legend:{display:!1},events:[],animation:{easing:"easeOutBounce"},tooltips:{enabled:!1},responsive:!1,cutoutPercentage:60}})})},f=function(b){var d,f={width:0,height:0,originalUrl:"",optimizedUrl:"",originalSize:0,optimizedSize:0,saving:0,modalAppendTo:a("body"),trigger:a('[data-target="imagify-visual-comparison"]'),modalId:"imagify-visual-comparison",openModal:!1},g=a.extend({},f,b);if(0===g.width||0===g.height||""===g.originalUrl||""===g.optimizedUrl||0===g.originalSize||0===g.optimizedSize||0===g.saving)return"error";d='',d+='
',d+='
',d+='
',d+='
',d+="
",d+='
',d+='
',d+='
',d+=''+imagifyTTT.labels.filesize+" ",d+=''+g.originalSize+" ",d+="
",d+="
",d+='
',d+='
',d+=''+imagifyTTT.labels.filesize+" ",d+=''+g.optimizedSize+" ",d+="
",d+='
',d+=''+imagifyTTT.labels.saving+" ",d+=''+g.saving+" % ",d+="
",d+="
",d+="
",d+='
'+imagifyTTT.labels.close+" ",d+="
",d+="
",g.modalAppendTo.append(d),g.trigger.on("click.imagify",function(b){var d,f,h=a(a(this).data("target")),i=0;b.preventDefault(),g.openModal&&c.imagify.openModal(a(this)),h.find(".imagify-modal-content").css({width:.85*a(c).outerWidth()+"px","max-width":g.width}),h.find(".imagify-img-before").on("load",function(){i++}).attr("src",g.originalUrl),h.find(".imagify-img-after").on("load",function(){i++}).attr("src",g.optimizedUrl+(g.optimizedUrl.indexOf("?")>0?"&":"?")+"v="+Date.now()),d=h.find(".twentytwenty-container"),f=setInterval(function(){if(2===i)return d.twentytwenty({handlePosition:.3,orientation:"horizontal",labelBefore:imagifyTTT.labels.originalL,labelAfter:imagifyTTT.labels.optimizedL},function(){var b,f,g,i,j,k,l=a(c).height(),m=h.find(".twentytwenty-container").height(),n=h.find(".twentytwenty-wrapper").position().top;d.closest(".imagify-modal-content").hasClass("loaded")||(d.closest(".imagify-modal-content").removeClass("loading").addClass("loaded"),e(h.find(".imagify-level-optimized .imagify-chart canvas"))),l0&&a(c).outerWidth()<=800||(b=a(".twentytwenty-container"),d=0,f=b.data("loader"),g=b.data("label-original"),h=b.data("label-normal"),i=b.data("label-aggressive"),j=b.data("label-ultra"),k=b.data("original-label").replace(/\*\*/,"").replace(/\*\*/," "),l=b.data("original-alt"),m=b.data("original-img"),n=b.data("original-dim").split("x"),o=b.data("normal-alt"),p=b.data("normal-img"),q=b.data("normal-dim").split("x"),r=b.data("aggressive-alt"),s=b.data("aggressive-img"),t=b.data("aggressive-dim").split("x"),u=b.data("ultra-label").replace(/\*\*/,"").replace(/\*\*/," "),v=b.data("ultra-alt"),w=b.data("ultra-img"),x=b.data("ultra-dim").split("x"),y='',y+=''+g+" ",y+=''+h+" ",y+=''+i+" ",y+=" ",z='',z+=''+h+" ",z+=''+i+" ",z+=''+j+" ",z+=" ",b.before(' '),a(".twentytwenty-left-buttons").append(y),a(".twentytwenty-right-buttons").append(z),A=' ',A+=' ',A+=' ',A+=' ',A+=a(".twentytwenty-left-buttons").lenght?y+z:"",b.closest(".imagify-modal-content").addClass("loading").find(".twentytwenty-container").append(A),a(".img-original").on("load",function(){d++}).attr("src",m),a(".img-normal").on("load",function(){d++}).attr("src",p),a(".img-aggressive").on("load",function(){d++}).attr("src",s),a(".img-ultra").on("load",function(){d++}).attr("src",w),B=setInterval(function(){4===d&&(b.twentytwenty({handlePosition:.6,orientation:"horizontal",labelBefore:k,labelAfter:u},function(){b.closest(".imagify-modal-content").hasClass("loaded")||(b.closest(".imagify-modal-content").removeClass("loading").addClass("loaded"),e(a(".imagify-level-ultra .imagify-chart canvas")))}),clearInterval(B),B=null)},75),a(".imagify-comparison-title").on("click",".twentytwenty-duo-buttons button:not(.selected)",function(b){var c,d=a(this),f=d.closest(".imagify-comparison-title").nextAll(".twentytwenty-wrapper").find(".twentytwenty-container"),g=d.closest(".twentytwenty-duo-buttons").hasClass("twentytwenty-duo-left")?"left":"right",h="left"===g?d.closest(".imagify-comparison-title").find(".twentytwenty-duo-right"):d.closest(".imagify-comparison-title").find(".twentytwenty-duo-left"),i=d.closest(".twentytwenty-duo-buttons").find("button"),j=f.find(".twentytwenty-before"),k=f.find(".twentytwenty-after"),l=d.data("img");b.stopPropagation(),b.preventDefault(),i.removeClass("selected"),d.addClass("selected"),h.find(".selected").data("img")===l&&h.find("button:not(.selected)").eq(0).trigger("click"),"left"===g&&(c=j.css("clip"),j.attr("style",""),j.removeClass("twentytwenty-before"),f.find(".img-"+l).addClass("twentytwenty-before").css("clip",c),a(".twentytwenty-before-label .twentytwenty-label-content").text(f.data(l+"-label")),a(".imagify-c-level.go-left").attr("aria-hidden","true").removeClass("go-left go-right"),a(".imagify-level-"+l).attr("aria-hidden","false").addClass("go-left")),"right"===g&&(k.removeClass("twentytwenty-after"),f.find(".img-"+l).addClass("twentytwenty-after"),a(".twentytwenty-after-label .twentytwenty-label-content").text(f.data(l+"-label")),a(".imagify-c-level.go-right").attr("aria-hidden","true").removeClass("go-left go-right"),a(".imagify-level-"+l).attr("aria-hidden","false").addClass("go-right")),e(a(".imagify-level-"+l+" .imagify-chart canvas"))})))}),imagifyTTT.imageWidth&&a(".post-php .wp_attachment_image .thumbnail").length>0){var g,h,i=a(".post-php .wp_attachment_image"),j={src:a("#imagify-full-original").val(),size:a("#imagify-full-original-size").val()},k=a("#misc-publishing-actions").find(".misc-pub-imagify .button-primary");imagifyTTT.widthLimit=parseInt(imagifyTTT.widthLimit,10),imagifyTTT.imageWidth>imagifyTTT.widthLimit&&j.src?(g=a(".misc-pub-filesize strong").text(),h=a(".imagify-data-item .imagify-chart-value").text(),a('[id^="imgedit-open-btn-"]').before(''+imagifyTTT.labels.compare+" "),f({width:parseInt(imagifyTTT.imageWidth,10),height:parseInt(imagifyTTT.imageHeight,10),originalUrl:j.src,optimizedUrl:imagifyTTT.imageSrc,originalSize:j.size,optimizedSize:g,saving:h,modalAppendTo:i,trigger:a("#imagify-start-comparison"),modalId:"imagify-visual-comparison"})):imagifyTTT.imageWidth0&&""===j.src||1===a("#misc-publishing-actions").find(".misc-pub-imagify .button-primary").length&&(a('[id^="imgedit-open-btn-"]').before(''+imagifyTTT.labels.optimize+" "),a("#imagify-optimize-trigger").on("click",function(){a(this).prev(".spinner").removeClass("imagify-hidden").addClass("is-active")}))}if(a(".upload-php .imagify-compare-images").length>0&&a(".imagify-compare-images").each(function(){var b=a(this),c=b.data("id"),d=b.closest("#post-"+c).find(".column-imagify_optimized_file");f({width:parseInt(b.data("full-width"),10),height:parseInt(b.data("full-height"),10),originalUrl:b.data("backup-src"),optimizedUrl:b.data("full-src"),originalSize:d.find(".original").text(),optimizedSize:d.find(".imagify-data-item .big").text(),saving:d.find(".imagify-chart-value").text(),modalAppendTo:b.closest(".column-primary"),trigger:b,modalId:"imagify-comparison-"+c})}),a(".upload-php").length>0){var l=function(){var b=setInterval(function(){var c,d,e;a(".media-modal .imagify-datas-details").length&&(d=a("#imagify-original-src").val(),d&&(e=a(".media-frame-content .attachment-actions"),e.find("#imagify-media-frame-comparison-btn").remove(),e.prepend(''+imagifyTTT.labels.compare+" "),c=a(".media-frame-content .compat-field-imagify"),f({width:parseInt(a("#imagify-full-width").val(),10),height:parseInt(a("#imagify-full-height").val(),10),originalUrl:d,optimizedUrl:a("#imagify-full-src").val(),originalSize:a("#imagify-original-size").val(),optimizedSize:c.find(".imagify-data-item .big").text(),saving:c.find(".imagify-chart-value").text(),modalAppendTo:a(".media-frame-content .thumbnail-image"),trigger:a("#imagify-media-frame-comparison-btn"),modalId:"imagify-comparison-modal",openModal:!0})),clearInterval(b),b=null)},20)};a(".upload-php").on("click",".media-frame.mode-grid .attachment, .edit-media-header .left, .edit-media-header .right",function(){l()}),function(a){var b={};return c.location.href.replace(/[?&]+([^=&]+)=?([^&]*)?/gi,function(a,c,d){b[c]=void 0!==d?d:""}),a?b[a]?b[a]:null:b}("item")&&l()}if(a("#imagify-files-list-form").length>0){var m=function(b){b.each(function(){var b=a(this),c=b.data("id"),d=b.closest("tr").find(".column-optimization .imagify-data-item");a("#imagify-comparison-"+c).remove(),f({width:parseInt(b.data("full-width"),10),height:parseInt(b.data("full-height"),10),originalUrl:b.data("backup-src"),optimizedUrl:b.data("full-src"),originalSize:d.find(".original").text(),optimizedSize:d.find(".optimized").text(),saving:d.find(".imagify-chart-value").text(),modalAppendTo:b.closest(".column-primary"),trigger:b,modalId:"imagify-comparison-"+c})})};a(c).on("comparisonprinted.imagify",function(b,c){var d;c=c||0,d=c?a("#imagify-files-list-form").find('.imagify-compare-images[data-id="'+c+'"]'):a("#imagify-files-list-form").find(".imagify-compare-images"),d.length&&m(d)}).trigger("comparisonprinted.imagify")}}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/library.js b/wp-content/plugins/imagify/assets/js/library.js
new file mode 100644
index 00000000..af7aec61
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/library.js
@@ -0,0 +1,51 @@
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ var bulkOpt;
+ /**
+ * Add a "Imagify'em all" in the select list.
+ */
+ bulkOpt = '' + imagifyLibrary.labels.bulkActionsOptimize + ' ';
+
+ if ( $( '.button-imagify-optimize-missing-sizes' ).length ) {
+ // If we have items that have missing sizes.
+ bulkOpt += '' + imagifyLibrary.labels.bulkActionsOptimizeMissingSizes + ' ';
+ }
+
+ if ( imagifyLibrary.backupOption || $( '.attachment-has-backup' ).length ) {
+ // If the backup option is enabled, or if we have items that can be restored.
+ bulkOpt += '' + imagifyLibrary.labels.bulkActionsRestore + ' ';
+ }
+
+ $( '.bulkactions select[name="action"] option:last-child' ).before( bulkOpt );
+ $( '.bulkactions select[name="action2"] option:last-child' ).before( bulkOpt );
+ $( '#bulkaction option:last-child' ).after( bulkOpt );
+
+ /**
+ * Process optimization for all selected images.
+ */
+ $( '#doaction' )
+ .add( '#doaction2' )
+ .add( '#bulkaction + [name="showThickbox"]' )
+ .on( 'click', function( e ) {
+ var value = $( this ).prev( 'select' ).val().split( '|' ),
+ action, ids;
+
+ if ( 'imagify-bulk' !== value[0] ) {
+ return;
+ }
+
+ e.preventDefault();
+
+ action = value[1];
+ ids = $( 'input[name^="media"]:checked, input[name^="doaction"]:checked' ).map( function() {
+ return this.value;
+ } ).get();
+
+ ids.forEach( function( id, index ) {
+ setTimeout( function() {
+ $( 'table .imagify-data-actions-container[data-id="' + id + '"] .button-imagify-' + action ).first().trigger( 'click' );
+ }, index * 300 );
+ } );
+ } );
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/library.min.js b/wp-content/plugins/imagify/assets/js/library.min.js
new file mode 100644
index 00000000..15137a7d
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/library.min.js
@@ -0,0 +1 @@
+!function(a,b,c,d){var e;e=''+imagifyLibrary.labels.bulkActionsOptimize+" ",a(".button-imagify-optimize-missing-sizes").length&&(e+=''+imagifyLibrary.labels.bulkActionsOptimizeMissingSizes+" "),(imagifyLibrary.backupOption||a(".attachment-has-backup").length)&&(e+=''+imagifyLibrary.labels.bulkActionsRestore+" "),a('.bulkactions select[name="action"] option:last-child').before(e),a('.bulkactions select[name="action2"] option:last-child').before(e),a("#bulkaction option:last-child").after(e),a("#doaction").add("#doaction2").add('#bulkaction + [name="showThickbox"]').on("click",function(b){var c,d,e=a(this).prev("select").val().split("|");"imagify-bulk"===e[0]&&(b.preventDefault(),c=e[1],d=a('input[name^="media"]:checked, input[name^="doaction"]:checked').map(function(){return this.value}).get(),d.forEach(function(b,d){setTimeout(function(){a('table .imagify-data-actions-container[data-id="'+b+'"] .button-imagify-'+c).first().trigger("click")},300*d)}))})}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/media-modal.js b/wp-content/plugins/imagify/assets/js/media-modal.js
new file mode 100644
index 00000000..a64b8f0f
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/media-modal.js
@@ -0,0 +1,334 @@
+/**
+ * Mini chart.
+ *
+ * @param {element} canvas The canvas element.
+ */
+window.imagify.drawMeAChart = function( canvas ) {
+ canvas.each( function() {
+ var value = parseInt( jQuery( this ).closest( '.imagify-chart' ).next( '.imagify-chart-value' ).text(), 10 );
+
+ new window.imagify.Chart( this, { // eslint-disable-line no-new
+ type: 'doughnut',
+ data: {
+ datasets: [ {
+ data: [ value, 100 - value ],
+ backgroundColor: [ '#00B3D3', '#D8D8D8' ],
+ borderColor: '#fff',
+ borderWidth: 1
+ } ]
+ },
+ options: {
+ legend: {
+ display: false
+ },
+ events: [],
+ animation: {
+ easing: 'easeOutBounce'
+ },
+ tooltips: {
+ enabled: false
+ },
+ responsive: false
+ }
+ } );
+ } );
+};
+
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ w.imagify.modal = {
+
+ working: [],
+
+ /*
+ * Init.
+ */
+ init: function () {
+ var $document = $( d ),
+ $processing;
+
+ // Update the chart in the media modal when a media is selected, and the ones already printed.
+ $( w ).on( 'canvasprinted.imagify', this.updateChart ).trigger( 'canvasprinted.imagify' );
+
+ // Toggle slide in custom column.
+ $( '.imagify-datas-details' ).hide();
+
+ $document.on( 'click', '.imagify-datas-more-action a', this.toggleSlide );
+
+ // Optimize, restore, etc.
+ $document.on( 'click', '.button-imagify-restore, .button-imagify-optimize, .button-imagify-manual-reoptimize, .button-imagify-optimize-missing-sizes, .button-imagify-generate-webp, .button-imagify-delete-webp', this.processOptimization );
+
+ $document.on( 'imagifybeat-send', this.addToImagifybeat );
+ $document.on( 'imagifybeat-tick', this.processImagifybeat );
+
+ // Some items may be processed in background on page load.
+ $processing = $( '.imagify-data-actions-container .button-imagify-processing' );
+
+ if ( $processing.length ) {
+ // Some media are already being processed.
+ // Lock the items, so we can check their status with Imagifybeat.
+ $processing.closest( '.imagify-data-actions-container' ).each( function() {
+ var $this = $( this ),
+ id = w.imagify.modal.sanitizeId( $this.data( 'id' ) ),
+ context = w.imagify.modal.sanitizeContext( $this.data( 'context' ) );
+
+ w.imagify.modal.lockItem( context, id );
+ } );
+
+ // Fasten Imagifybeat.
+ w.imagify.beat.interval( 15 );
+ }
+ },
+
+ // Charts ==================================================================================
+
+ /**
+ * Update the chart in the media modal when a media is selected, and the ones already printed.
+ *
+ * @param {object} e Event.
+ * @param {string} selector A CSS selector.
+ */
+ updateChart: function( e, selector ) {
+ var $canvas;
+
+ selector = selector || '.imagify-consumption-chart';
+ $canvas = $( selector );
+
+ w.imagify.drawMeAChart( $canvas );
+
+ $canvas.closest( '.imagify-datas-list' ).siblings( '.imagify-datas-details' ).hide();
+ },
+
+ // Optimization ============================================================================
+
+ /**
+ * Process to one of these actions: restore, optimize, re-optimize, or optimize missing sizes.
+ *
+ * @param {object} e Event.
+ */
+ processOptimization: function( e ) {
+ var $obj = $( this ),
+ $container = $obj.parents( '.imagify-data-actions-container' ),
+ id = w.imagify.modal.sanitizeId( $container.data( 'id' ) ),
+ context = w.imagify.modal.sanitizeContext( $container.data( 'context' ) ),
+ href, processingTemplate;
+
+ e.preventDefault();
+
+ if ( w.imagify.modal.isItemLocked( context, id ) ) {
+ return;
+ }
+
+ w.imagify.modal.lockItem( context, id );
+
+ href = $obj.attr( 'href' );
+ processingTemplate = w.imagify.template( 'imagify-button-processing' );
+
+ $container.html( processingTemplate( {
+ label: $obj.data( 'processing-label' )
+ } ) );
+
+ $.get( href.replace( 'admin-post.php', 'admin-ajax.php' ) )
+ .done( function( response ) {
+ if ( response.data && response.data.html ) {
+ // The work is done.
+ w.imagify.modal.displayProcessResult( context, id, response.data.html );
+ } else {
+ // Still processing in background: we're waiting for the result by poking Imagifybeat.
+ // Set the Imagifybeat interval to 15 seconds.
+ w.imagify.beat.interval( 15 );
+ }
+ } );
+ },
+
+ // Imagifybeat =============================================================================
+
+ /**
+ * Send the media IDs and their status to Imagifybeat.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ addToImagifybeat: function ( e, data ) {
+ var $containers = $( '.imagify-data-actions-container' );
+
+ if ( ! $containers.length ) {
+ return;
+ }
+
+ data[ w.imagifyModal.imagifybeatID ] = {};
+
+ $containers.each( function() {
+ var $this = $( this ),
+ id = w.imagify.modal.sanitizeId( $this.data( 'id' ) ),
+ context = w.imagify.modal.sanitizeContext( $this.data( 'context' ) ),
+ locked = w.imagify.modal.isItemLocked( context, id ) ? 1 : 0;
+
+ data[ w.imagifyModal.imagifybeatID ][ context ] = data[ w.imagifyModal.imagifybeatID ][ context ] || {};
+ data[ w.imagifyModal.imagifybeatID ][ context ][ '_' + id ] = locked;
+ } );
+ },
+
+ /**
+ * Listen for the custom event "imagifybeat-tick" on $(document).
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ processImagifybeat: function ( e, data ) {
+ if ( typeof data[ w.imagifyModal.imagifybeatID ] === 'undefined' ) {
+ return;
+ }
+
+ $.each( data[ w.imagifyModal.imagifybeatID ], function( contextId, htmlContent ) {
+ var context, id;
+
+ context = $.trim( contextId ).match( /^(.+)_(\d+)$/ );
+
+ if ( ! context ) {
+ return;
+ }
+
+ id = w.imagify.modal.sanitizeId( context[2] );
+ context = w.imagify.modal.sanitizeContext( context[1] );
+
+ w.imagify.modal.displayProcessResult( context, id, htmlContent );
+ } );
+ },
+
+ // DOM manipulation tools ==================================================================
+
+ /**
+ * Display the process result.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ * @param {string} htmlContent The HTML to insert.
+ */
+ displayProcessResult: function( context, id, htmlContent ) {
+ var $containers = w.imagify.modal.getContainers( context, id );
+
+ $containers.html( htmlContent );
+ w.imagify.modal.unlockItem( context, id );
+
+ if ( ! w.imagify.modal.working.length ) {
+ // Work is done.
+ // Open the last container being processed.
+ w.imagify.modal.openSlide( $containers );
+ // Reset Imagifybeat interval.
+ w.imagify.beat.resetInterval();
+ }
+ },
+
+ /**
+ * Open a slide rapidly.
+ *
+ * @param {object} $containers A jQuery collection.
+ */
+ openSlide: function( $containers ) {
+ $containers.each( function() {
+ var $container = $( this ),
+ text = $container.find( '.imagify-datas-more-action a' ).data( 'close' );
+
+ $container.find( '.imagify-datas-more-action a' ).addClass( 'is-open' ).find( '.the-text' ).text( text );
+ $container.find( '.imagify-datas-details' ).show().addClass( 'is-open' );
+ } );
+ },
+
+ /**
+ * Toggle slide in custom column.
+ *
+ * @param {object} e Event.
+ */
+ toggleSlide: function( e ) {
+ var $this = $( this );
+
+ e.preventDefault();
+
+ if ( $this.hasClass( 'is-open' ) ) {
+ $( $this.attr( 'href' ) ).slideUp( 300 ).removeClass( 'is-open' );
+ $this.removeClass( 'is-open' ).find( '.the-text' ).text( $this.data( 'open' ) );
+ } else {
+ $( $this.attr( 'href' ) ).slideDown( 300 ).addClass( 'is-open' );
+ $this.addClass( 'is-open' ).find( '.the-text' ).text( $this.data( 'close' ) );
+ }
+ },
+
+ /**
+ * Get all containers matching the given context and id.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ * @return {object} A jQuery collection.
+ */
+ getContainers: function( context, id ) {
+ return $( '.imagify-data-actions-container[data-id="' + id + '"][data-context="' + context + '"]' );
+ },
+
+ // Sanitization ============================================================================
+
+ /**
+ * Sanitize a media ID.
+ *
+ * @param {int|string} id A media ID.
+ * @return {int}
+ */
+ sanitizeId: function( id ) {
+ return parseInt( id, 10 );
+ },
+
+ /**
+ * Sanitize a media context.
+ *
+ * @param {string} context A media context.
+ * @return {string}
+ */
+ sanitizeContext: function( context ) {
+ context = context.replace( '/[^a-z0-9_-]/gi', '' ).toLowerCase();
+ return context ? context : 'wp';
+ },
+
+ // Locks ===================================================================================
+
+ /**
+ * Lock an item.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ */
+ lockItem: function( context, id ) {
+ if ( ! this.isItemLocked( context, id ) ) {
+ this.working.push( context + '_' + id );
+ }
+ },
+
+ /**
+ * Unlock an item.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ */
+ unlockItem: function( context, id ) {
+ var name = context + '_' + id,
+ i = _.indexOf( this.working, name );
+
+ if ( i > -1 ) {
+ this.working.splice( i, 1 );
+ }
+ },
+
+ /**
+ * Tell if an item is locked.
+ *
+ * @param {string} context The media context.
+ * @param {int} id The media ID.
+ * @return {bool}
+ */
+ isItemLocked: function( context, id ) {
+ return _.indexOf( this.working, context + '_' + id ) > -1;
+ }
+ };
+
+ w.imagify.modal.init();
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/media-modal.min.js b/wp-content/plugins/imagify/assets/js/media-modal.min.js
new file mode 100644
index 00000000..18d1bfdd
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/media-modal.min.js
@@ -0,0 +1 @@
+window.imagify.drawMeAChart=function(a){a.each(function(){var a=parseInt(jQuery(this).closest(".imagify-chart").next(".imagify-chart-value").text(),10);new window.imagify.Chart(this,{type:"doughnut",data:{datasets:[{data:[a,100-a],backgroundColor:["#00B3D3","#D8D8D8"],borderColor:"#fff",borderWidth:1}]},options:{legend:{display:!1},events:[],animation:{easing:"easeOutBounce"},tooltips:{enabled:!1},responsive:!1}})})},function(a,b,c,d){c.imagify.modal={working:[],init:function(){var d,e=a(b);a(c).on("canvasprinted.imagify",this.updateChart).trigger("canvasprinted.imagify"),a(".imagify-datas-details").hide(),e.on("click",".imagify-datas-more-action a",this.toggleSlide),e.on("click",".button-imagify-restore, .button-imagify-optimize, .button-imagify-manual-reoptimize, .button-imagify-optimize-missing-sizes, .button-imagify-generate-webp, .button-imagify-delete-webp",this.processOptimization),e.on("imagifybeat-send",this.addToImagifybeat),e.on("imagifybeat-tick",this.processImagifybeat),d=a(".imagify-data-actions-container .button-imagify-processing"),d.length&&(d.closest(".imagify-data-actions-container").each(function(){var b=a(this),d=c.imagify.modal.sanitizeId(b.data("id")),e=c.imagify.modal.sanitizeContext(b.data("context"));c.imagify.modal.lockItem(e,d)}),c.imagify.beat.interval(15))},updateChart:function(b,d){var e;d=d||".imagify-consumption-chart",e=a(d),c.imagify.drawMeAChart(e),e.closest(".imagify-datas-list").siblings(".imagify-datas-details").hide()},processOptimization:function(b){var d,e,f=a(this),g=f.parents(".imagify-data-actions-container"),h=c.imagify.modal.sanitizeId(g.data("id")),i=c.imagify.modal.sanitizeContext(g.data("context"));b.preventDefault(),c.imagify.modal.isItemLocked(i,h)||(c.imagify.modal.lockItem(i,h),d=f.attr("href"),e=c.imagify.template("imagify-button-processing"),g.html(e({label:f.data("processing-label")})),a.get(d.replace("admin-post.php","admin-ajax.php")).done(function(a){a.data&&a.data.html?c.imagify.modal.displayProcessResult(i,h,a.data.html):c.imagify.beat.interval(15)}))},addToImagifybeat:function(b,d){var e=a(".imagify-data-actions-container");e.length&&(d[c.imagifyModal.imagifybeatID]={},e.each(function(){var b=a(this),e=c.imagify.modal.sanitizeId(b.data("id")),f=c.imagify.modal.sanitizeContext(b.data("context")),g=c.imagify.modal.isItemLocked(f,e)?1:0;d[c.imagifyModal.imagifybeatID][f]=d[c.imagifyModal.imagifybeatID][f]||{},d[c.imagifyModal.imagifybeatID][f]["_"+e]=g}))},processImagifybeat:function(b,d){void 0!==d[c.imagifyModal.imagifybeatID]&&a.each(d[c.imagifyModal.imagifybeatID],function(b,d){var e,f;(e=a.trim(b).match(/^(.+)_(\d+)$/))&&(f=c.imagify.modal.sanitizeId(e[2]),e=c.imagify.modal.sanitizeContext(e[1]),c.imagify.modal.displayProcessResult(e,f,d))})},displayProcessResult:function(a,b,d){var e=c.imagify.modal.getContainers(a,b);e.html(d),c.imagify.modal.unlockItem(a,b),c.imagify.modal.working.length||(c.imagify.modal.openSlide(e),c.imagify.beat.resetInterval())},openSlide:function(b){b.each(function(){var b=a(this),c=b.find(".imagify-datas-more-action a").data("close");b.find(".imagify-datas-more-action a").addClass("is-open").find(".the-text").text(c),b.find(".imagify-datas-details").show().addClass("is-open")})},toggleSlide:function(b){var c=a(this);b.preventDefault(),c.hasClass("is-open")?(a(c.attr("href")).slideUp(300).removeClass("is-open"),c.removeClass("is-open").find(".the-text").text(c.data("open"))):(a(c.attr("href")).slideDown(300).addClass("is-open"),c.addClass("is-open").find(".the-text").text(c.data("close")))},getContainers:function(b,c){return a('.imagify-data-actions-container[data-id="'+c+'"][data-context="'+b+'"]')},sanitizeId:function(a){return parseInt(a,10)},sanitizeContext:function(a){return(a=a.replace("/[^a-z0-9_-]/gi","").toLowerCase())||"wp"},lockItem:function(a,b){this.isItemLocked(a,b)||this.working.push(a+"_"+b)},unlockItem:function(a,b){var c=a+"_"+b,d=_.indexOf(this.working,c);d>-1&&this.working.splice(d,1)},isItemLocked:function(a,b){return _.indexOf(this.working,a+"_"+b)>-1}},c.imagify.modal.init()}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/notices.js b/wp-content/plugins/imagify/assets/js/notices.js
new file mode 100644
index 00000000..6ca397b9
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/notices.js
@@ -0,0 +1,127 @@
+// All notices =====================================================================================
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ /**
+ * Close an Imagify notice.
+ */
+ $( '.imagify-notice-dismiss' ).on( 'click.imagify', function( e ) {
+ var $this = $( this ),
+ $parent = $this.parents( '.imagify-welcome, .imagify-notice, .imagify-rkt-notice' ),
+ href = $this.attr( 'href' );
+
+ e.preventDefault();
+
+ // Hide the notice.
+ $parent.fadeTo( 100 , 0, function() {
+ $( this ).slideUp( 100, function() {
+ $( this ).remove();
+ } );
+ } );
+
+ // Save the dismiss notice.
+ $.get( href.replace( 'admin-post.php', 'admin-ajax.php' ) );
+ } );
+
+} )(jQuery, document, window);
+
+
+// The "welcome steps" notice + "wrong API key" notice =============================================
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ /**
+ * 1. Create a new Imagify account.
+ */
+ $( '#imagify-signup' ).on( 'click.imagify', function( e ) {
+ e.preventDefault();
+
+ // Display the sign up form.
+ swal( {
+ title: imagifyNotices.labels.signupTitle,
+ html: imagifyNotices.labels.signupText,
+ confirmButtonText: imagifyNotices.labels.signupConfirmButtonText,
+ input: 'email',
+ padding: 0,
+ showLoaderOnConfirm: true,
+ customClass: 'imagify-sweet-alert imagify-sweet-alert-signup',
+ inputValidator: function( inputValue ) {
+ return new Promise( function( resolve, reject ) {
+ if ( $.trim( inputValue ) === '' || ! inputValue ) {
+ reject( imagifyNotices.labels.signupErrorEmptyEmail );
+ } else {
+ resolve();
+ }
+ } );
+ },
+ preConfirm: function( inputValue ) {
+ return new Promise( function( resolve, reject ) {
+ setTimeout( function() {
+ $.get( ajaxurl + w.imagify.concat + 'action=imagify_signup&email=' + inputValue + '&imagifysignupnonce=' + $( '#imagifysignupnonce' ).val() )
+ .done( function( response ) {
+ if ( ! response.success ) {
+ reject( response.data );
+ } else {
+ resolve();
+ }
+ } );
+ }, 2000 );
+ } );
+ },
+ } ).then( function() {
+ swal( {
+ title: imagifyNotices.labels.signupSuccessTitle,
+ html: imagifyNotices.labels.signupSuccessText,
+ type: 'success',
+ padding: 0,
+ customClass: 'imagify-sweet-alert'
+ } );
+ } );
+ } );
+
+ /**
+ * 2. Check and save the Imagify API Key.
+ */
+ $( '#imagify-save-api-key' ).on( 'click.imagify', function( e ) {
+ e.preventDefault();
+
+ // Display the API key form.
+ swal( {
+ title: imagifyNotices.labels.saveApiKeyTitle,
+ html: imagifyNotices.labels.saveApiKeyText,
+ confirmButtonText: imagifyNotices.labels.saveApiKeyConfirmButtonText,
+ input: 'text',
+ padding: 0,
+ showLoaderOnConfirm: true,
+ customClass: 'imagify-sweet-alert imagify-sweet-alert-signup',
+ inputValidator: function( inputValue ) {
+ return new Promise( function( resolve, reject ) {
+ if ( $.trim( inputValue ) === '' || ! inputValue ) {
+ reject( imagifyNotices.labels.ApiKeyErrorEmpty );
+ } else {
+ resolve();
+ }
+ } );
+ },
+ preConfirm: function( inputValue ) {
+ return new Promise( function( resolve, reject ) {
+ $.get( ajaxurl + w.imagify.concat + 'action=imagify_check_api_key_validity&api_key=' + inputValue + '&imagifycheckapikeynonce=' + $( '#imagifycheckapikeynonce' ).val() )
+ .done( function( response ) {
+ if ( ! response.success ) {
+ reject( response.data );
+ } else {
+ resolve();
+ }
+ } );
+ } );
+ },
+ } ).then( function() {
+ swal( {
+ title: imagifyNotices.labels.ApiKeyCheckSuccessTitle,
+ html: imagifyNotices.labels.ApiKeyCheckSuccessText,
+ type: 'success',
+ padding: 0,
+ customClass: 'imagify-sweet-alert'
+ } );
+ } );
+ } );
+
+} )(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/notices.min.js b/wp-content/plugins/imagify/assets/js/notices.min.js
new file mode 100644
index 00000000..941ef908
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/notices.min.js
@@ -0,0 +1 @@
+!function(a,b,c,d){a(".imagify-notice-dismiss").on("click.imagify",function(b){var c=a(this),d=c.parents(".imagify-welcome, .imagify-notice, .imagify-rkt-notice"),e=c.attr("href");b.preventDefault(),d.fadeTo(100,0,function(){a(this).slideUp(100,function(){a(this).remove()})}),a.get(e.replace("admin-post.php","admin-ajax.php"))})}(jQuery,document,window),function(a,b,c,d){a("#imagify-signup").on("click.imagify",function(b){b.preventDefault(),swal({title:imagifyNotices.labels.signupTitle,html:imagifyNotices.labels.signupText,confirmButtonText:imagifyNotices.labels.signupConfirmButtonText,input:"email",padding:0,showLoaderOnConfirm:!0,customClass:"imagify-sweet-alert imagify-sweet-alert-signup",inputValidator:function(b){return new Promise(function(c,d){""!==a.trim(b)&&b?c():d(imagifyNotices.labels.signupErrorEmptyEmail)})},preConfirm:function(b){return new Promise(function(d,e){setTimeout(function(){a.get(ajaxurl+c.imagify.concat+"action=imagify_signup&email="+b+"&imagifysignupnonce="+a("#imagifysignupnonce").val()).done(function(a){a.success?d():e(a.data)})},2e3)})}}).then(function(){swal({title:imagifyNotices.labels.signupSuccessTitle,html:imagifyNotices.labels.signupSuccessText,type:"success",padding:0,customClass:"imagify-sweet-alert"})})}),a("#imagify-save-api-key").on("click.imagify",function(b){b.preventDefault(),swal({title:imagifyNotices.labels.saveApiKeyTitle,html:imagifyNotices.labels.saveApiKeyText,confirmButtonText:imagifyNotices.labels.saveApiKeyConfirmButtonText,input:"text",padding:0,showLoaderOnConfirm:!0,customClass:"imagify-sweet-alert imagify-sweet-alert-signup",inputValidator:function(b){return new Promise(function(c,d){""!==a.trim(b)&&b?c():d(imagifyNotices.labels.ApiKeyErrorEmpty)})},preConfirm:function(b){return new Promise(function(d,e){a.get(ajaxurl+c.imagify.concat+"action=imagify_check_api_key_validity&api_key="+b+"&imagifycheckapikeynonce="+a("#imagifycheckapikeynonce").val()).done(function(a){a.success?d():e(a.data)})})}}).then(function(){swal({title:imagifyNotices.labels.ApiKeyCheckSuccessTitle,html:imagifyNotices.labels.ApiKeyCheckSuccessText,type:"success",padding:0,customClass:"imagify-sweet-alert"})})})}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/options.js b/wp-content/plugins/imagify/assets/js/options.js
new file mode 100644
index 00000000..0a37c453
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/options.js
@@ -0,0 +1,1183 @@
+window.imagify = window.imagify || {};
+
+(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+ /*
+ * Process an API key check validity.
+ */
+ var busy = false,
+ xhr = false;
+
+ $( '#imagify-settings #api_key' ).on( 'blur.imagify', function() {
+ var obj = $( this ),
+ value = obj.val();
+
+ if ( $.trim( value ) === '' ) {
+ return false;
+ }
+
+ if ( $( '#check_api_key' ).val() === value ) {
+ $( '#imagify-check-api-container' ).html( ' ' + imagifyOptions.labels.ValidApiKeyText );
+ return false;
+ }
+
+ if ( true === busy ) {
+ xhr.abort();
+ } else {
+ $( '#imagify-check-api-container' ).remove();
+ obj.after( ' ' + imagifyOptions.labels.waitApiKeyCheckText + ' ' );
+ }
+
+ busy = true;
+
+ xhr = $.get( ajaxurl + w.imagify.concat + 'action=imagify_check_api_key_validity&api_key=' + obj.val() + '&imagifycheckapikeynonce=' + $( '#imagifycheckapikeynonce' ).val() )
+ .done( function( response ) {
+ if ( ! response.success ) {
+ $( '#imagify-check-api-container' ).html( ' ' + response.data );
+ } else {
+ // Success, the API key is valid.
+ $( '#imagify-check-api-container' ).remove();
+ swal( {
+ title: imagifyOptions.labels.ApiKeyCheckSuccessTitle,
+ html: imagifyOptions.labels.ApiKeyCheckSuccessText,
+ type: 'success',
+ padding: 0,
+ customClass: 'imagify-sweet-alert'
+ } ).then( function() {
+ location.reload();
+ } );
+ }
+
+ busy = false;
+ } );
+ } );
+
+ /**
+ * Check the boxes by clicking "labels" (aria-describedby items).
+ */
+ $( '.imagify-options-line' ).css( 'cursor', 'pointer' ).on( 'click.imagify', function( e ) {
+ if ( 'INPUT' === e.target.nodeName ) {
+ return;
+ }
+ $( 'input[aria-describedby="' + $( this ).attr( 'id' ) + '"]' ).trigger( 'click.imagify' );
+ } );
+
+ $( '.imagify-settings th span' ).on( 'click.imagify', function() {
+ var $input = $( this ).parent().next( 'td' ).find( ':checkbox' );
+
+ if ( 1 === $input.length ) {
+ $input.trigger( 'click.imagify' );
+ }
+ } );
+
+ /**
+ * Auto check on options-line input value change.
+ */
+ $( '.imagify-options-line' ).find( 'input' ).on( 'change.imagify focus.imagify', function() {
+ var $checkbox;
+
+ if ( 'checkbox' === this.type && ! this.checked ) {
+ return;
+ }
+
+ $checkbox = $( this ).closest( '.imagify-options-line' ).prev( 'label' ).prev( ':checkbox' );
+
+ if ( $checkbox.length && ! $checkbox[0].checked ) {
+ $checkbox.prop( 'checked', true );
+ }
+ } );
+
+ /**
+ * Imagify Backup alert.
+ */
+ $( '[name="imagify_settings[backup]"]' ).on( 'change.imagify', function() {
+ var $_this = $( this ),
+ $backupMessage = $_this.siblings( '#backup-dir-is-writable' ),
+ params = {
+ 'action': 'imagify_check_backup_dir_is_writable',
+ '_wpnonce': $backupMessage.data( 'nonce' )
+ };
+
+ if ( $_this.is( ':checked' ) ) {
+ $.getJSON( ajaxurl, params )
+ .done( function( r ) {
+ if ( $.isPlainObject( r ) && r.success ) {
+ if ( r.data.is_writable ) {
+ // Hide the error message.
+ $backupMessage.addClass( 'hidden' );
+ } else {
+ // Show the error message.
+ $backupMessage.removeClass( 'hidden' );
+ }
+ }
+ } );
+ return;
+ }
+
+ // Are you sure? No backup?
+ swal( {
+ title: imagifyOptions.labels.noBackupTitle,
+ html: imagifyOptions.labels.noBackupText,
+ type: 'warning',
+ customClass: 'imagify-sweet-alert',
+ padding: 0,
+ showCancelButton: true,
+ cancelButtonText: imagifySwal.labels.cancelButtonText,
+ reverseButtons: true
+ } ).then(
+ function() {
+ // Leave it unchecked, hide the error message.
+ $backupMessage.addClass( 'hidden' );
+ },
+ function() {
+ // Re-check.
+ $_this.prop( 'checked', true );
+ }
+ );
+ } );
+
+ /**
+ * Fade CDN URL field.
+ */
+ $( '[name="imagify_settings[display_webp_method]"]' ).on( 'change.imagify init.imagify', function( e ) {
+ if ( 'picture' === e.target.value ) {
+ $( e.target ).closest( '.imagify-radio-group' ).next( '.imagify-options-line' ).removeClass( 'imagify-faded' );
+ } else {
+ $( e.target ).closest( '.imagify-radio-group' ).next( '.imagify-options-line' ).addClass( 'imagify-faded' );
+ }
+ } ).filter( ':checked' ).trigger( 'init.imagify' );
+
+} )(jQuery, document, window);
+
+
+// Display Imagify User data =======================================================================
+(function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ if ( ! w.imagifyUser ) {
+ return;
+ }
+
+ $.getJSON( ajaxurl, w.imagifyUser )
+ .done( function( r ) {
+ if ( $.isPlainObject( r ) && r.success ) {
+ r.data.id = null;
+ r.data.plan_id = null;
+ r.data.is = [];
+
+ $.each( r.data, function( k, v ) {
+ var htmlClass = '.imagify-user-' + k.replace( /_/g, '-' );
+
+ if ( k.indexOf( 'is_' ) === 0 ) {
+ if ( v ) {
+ r.data.is.push( htmlClass );
+ }
+ } else if ( 'is' !== k ) {
+ $( htmlClass ).text( v );
+ }
+ } );
+
+ r.data.is.push( 'best-plan' );
+
+ $( r.data.is.join( ',' ) ).removeClass( 'hidden' );
+ }
+ } );
+
+} )(window, document, jQuery);
+
+
+// Files tree for "custom folders" =================================================================
+(function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ if ( ! imagifyOptions.getFilesTree ) {
+ return;
+ }
+
+ function imagifyInsertFolderRow( value ) {
+ var added = false,
+ prevPath = null,
+ valueTest, template, $wrap, $rows, $field;
+
+ if ( ! value ) {
+ return;
+ }
+
+ $wrap = $( '#imagify-custom-folders-selected' );
+ $rows = $wrap.find( '.imagify-custom-folder-line' );
+ $field = $rows.find( '[value="' + value + '"]' );
+
+ if ( $field.length ) {
+ // Shouldn't happen.
+ return;
+ }
+
+ // Path #///# Label.
+ value = value.split( '#///#' );
+ valueTest = value[1].replace( /\/+$/,'' ).toLowerCase();
+ template = w.imagify.template( 'imagify-custom-folder' );
+
+ $rows.each( function() {
+ var $this = $( this ),
+ thisValueTest = $this.data( 'path' ).replace( /\/+$/,'' ).toLowerCase();
+
+ if ( '' !== thisValueTest && valueTest.indexOf( thisValueTest ) === 0 ) {
+ // We try to add a sub-folder of an already selected folder. It shouldn't happen though, since it can't be selected.
+ added = true;
+ return false;
+ } else if ( valueTest < thisValueTest ) {
+ $this.before( template( {
+ value: value[0],
+ label: value[1]
+ } ) );
+ $rows = $wrap.find( '.imagify-custom-folder-line' );
+ added = true;
+ return false;
+ }
+ } );
+
+ if ( ! added ) {
+ $wrap.append( template( {
+ value: value[0],
+ label: value[1]
+ } ) );
+ $rows = $wrap.find( '.imagify-custom-folder-line' );
+ }
+
+ // Remove sub-paths: if 'a/b/' and 'a/b/c/' are in the array, we keep only the "parent" 'a/b/'.
+ if ( '' !== valueTest ) {
+ $rows.each( function() {
+ var $this = $( this ),
+ thisPath = $this.data( 'path' ).toLowerCase();
+
+ if ( null !== prevPath && thisPath.indexOf( prevPath ) === 0 ) {
+ $this.find( '.imagify-custom-folders-remove' ).trigger( 'click.imagify' );
+ } else {
+ prevPath = thisPath;
+ }
+ } );
+ }
+
+ // Display a message.
+ $wrap.next( '.hidden' ).removeClass( 'hidden' );
+ }
+
+ // Clicking the main button: fetch site's root folders and files, then display them in a modal.
+ $( '#imagify-add-custom-folder' ).on( 'click.imagify', function() {
+ var $button = $( this ),
+ selected = [],
+ $folders;
+
+ if ( $button.attr( 'disabled' ) ) {
+ return;
+ }
+
+ $button.attr( 'disabled', 'disabled' ).next( 'img' ).attr( 'aria-hidden', 'false' );
+
+ $folders = $( '#imagify-custom-folders-selected' );
+
+ $folders.find( 'input' ).each( function() {
+ selected.push( this.value );
+ } );
+
+ $.post( imagifyOptions.getFilesTree, {
+ folder: '/',
+ selected: selected
+ }, null, 'json' )
+ .done( function( response ) {
+ if ( ! response.success ) {
+ swal( {
+ title: imagifyOptions.labels.error,
+ html: response.data || '',
+ type: 'error',
+ padding: 0,
+ customClass: 'imagify-sweet-alert'
+ } );
+ return;
+ }
+
+ swal( {
+ title: imagifyOptions.labels.filesTreeTitle,
+ html: '' + imagifyOptions.labels.filesTreeSubTitle + '
' + imagifyOptions.labels.cleaningInfo + '
',
+ type: '',
+ customClass: 'imagify-sweet-alert imagify-swal-has-subtitle imagify-folders-selection',
+ showCancelButton: true,
+ padding: 0,
+ confirmButtonText: imagifyOptions.labels.confirmFilesTreeBtn,
+ cancelButtonText: imagifySwal.labels.cancelButtonText,
+ reverseButtons: true
+ } ).then( function() {
+ var values = $( '#imagify-folders-tree input' ).serializeArray(); // Don't do `$( '#imagify-folders-tree' ).find( 'input' )`, it won't work.
+
+ if ( ! values.length ) {
+ return;
+ }
+
+ $.each( values, function( i, v ) {
+ imagifyInsertFolderRow( v.value );
+ } );
+ } ).catch( swal.noop );
+ } )
+ .fail( function() {
+ swal( {
+ title: imagifyOptions.labels.error,
+ type: 'error',
+ customClass: 'imagify-sweet-alert',
+ padding: 0
+ } );
+ } )
+ .always( function(){
+ $button.removeAttr( 'disabled' ).next( 'img' ).attr( 'aria-hidden', 'true' );
+ } );
+ } );
+
+ // Clicking a folder icon in the modal: fetch the folder's sub-folders and files, then display them.
+ $( d ).on( 'click.imagify', '#imagify-folders-tree [data-folder]', function() {
+ var $button = $( this ),
+ $tree = $button.nextAll( '.imagify-folders-sub-tree' ),
+ selected = [];
+
+ if ( $button.attr( 'disabled' ) || $button.siblings( ':checkbox' ).is( ':checked' ) ) {
+ return;
+ }
+
+ $button.attr( 'disabled', 'disabled' ).addClass( 'imagify-loading' );
+
+ if ( $tree.length ) {
+ if ( $button.hasClass( 'imagify-is-open' ) ) {
+ $tree.addClass( 'hidden' );
+ $button.removeClass(' imagify-is-open' );
+ } else {
+ $tree.removeClass( 'hidden' );
+ $button.addClass( 'imagify-is-open' );
+ }
+ $button.removeAttr( 'disabled' ).removeClass( 'imagify-loading' );
+ return;
+ }
+
+ $( '#imagify-custom-folders-selected' ).find( 'input' ).each( function() {
+ selected.push( this.value );
+ } );
+
+ $.post( imagifyOptions.getFilesTree, {
+ folder: $button.data( 'folder' ),
+ selected: selected
+ }, null, 'json' )
+ .done( function( response ) {
+ if ( ! response.success ) {
+ swal( {
+ title: imagifyOptions.labels.error,
+ html: response.data || '',
+ type: 'error',
+ padding: 0,
+ customClass: 'imagify-sweet-alert'
+ } );
+ return;
+ }
+
+ $button.addClass( 'imagify-is-open' ).parent().append( '' );
+ } )
+ .fail( function(){
+ swal( {
+ title: imagifyOptions.labels.error,
+ type: 'error',
+ padding: 0,
+ customClass: 'imagify-sweet-alert'
+ } );
+ } )
+ .always( function(){
+ $button.removeAttr( 'disabled' ).removeClass( 'imagify-loading' );
+ } );
+ } );
+
+ // Clicking a Remove folder button make it disappear.
+ $( '#imagify-custom-folders' ).on( 'click.imagify', '.imagify-custom-folders-remove', function() {
+ var $row = $( this ).closest( '.imagify-custom-folder-line' ).addClass( 'imagify-will-remove' );
+
+ w.setTimeout( function() {
+ $row.remove();
+ // Display a message.
+ $( '#imagify-custom-folders-selected' ).siblings( '.imagify-success.hidden' ).removeClass( 'hidden' );
+ }, 750 );
+ } );
+
+ // Clicking the "add themes to folders" button.
+ $( '#imagify-add-themes-to-custom-folder' ).on( 'click.imagify', function() {
+ var $this = $( this );
+
+ imagifyInsertFolderRow( $this.data( 'theme' ) );
+ imagifyInsertFolderRow( $this.data( 'theme-parent' ) );
+
+ // Remove clicked button.
+ $this.replaceWith( '' + imagifyOptions.labels.themesAdded + '
' );
+ } );
+
+} )(window, document, jQuery);
+
+
+// Generate missing webp versions ==================================================================
+/* eslint-disable no-underscore-dangle, consistent-this */
+(function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ if ( ! imagifyOptions.bulk ) {
+ return;
+ }
+
+ w.imagify.optionsBulk = {
+ // Properties ==============================================================================
+ /**
+ * When media IDs have been fetched for a context (or tried, with error), the context is removed from this list.
+ *
+ * @var {array} fetchQueue An array of contexts.
+ */
+ fetchQueue: [],
+ /**
+ * Contexts in queue.
+ *
+ * @var {array} queue An array of objects: {
+ * @type {string} context The context, like 'wp'.
+ * @type {string} optimizeURL The URL to ping to optimize a file.
+ * @type {array} mediaIDs A list of media IDs.
+ * }
+ */
+ queue: [],
+ /**
+ * List of medias being processed.
+ *
+ * @var {array} processingQueue An array of objects: {
+ * @type {string} context The context, like 'wp'.
+ * @type {int} mediaID The media ID.
+ * }
+ */
+ processingQueue: [],
+ /**
+ * Stores the first error ID or message that is occurring when fetching media IDs.
+ *
+ * @var {string|bool} error False if no error.
+ */
+ fetchError: false,
+ /**
+ * The current error ID or message.
+ *
+ * @var {string|bool} error False if no error.
+ */
+ error: false,
+ /**
+ * Set to true at the beginning of the process.
+ *
+ * @var {bool} working
+ */
+ working: false,
+ /**
+ * Set to true to stop the whole thing.
+ *
+ * @var {bool} processIsStopped
+ */
+ processIsStopped: true,
+ /**
+ * Total number of processed media.
+ *
+ * @var {int}
+ */
+ processedMedia: 0,
+ /**
+ * Total number of media to process.
+ *
+ * @var {int}
+ */
+ totalMedia: 0,
+ /**
+ * The button.
+ *
+ * @var {jQuery}
+ */
+ $button: null,
+ /**
+ * The progress bar wrapper.
+ *
+ * @var {jQuery}
+ */
+ $progressWrap: null,
+ /**
+ * The progress bar.
+ *
+ * @var {jQuery}
+ */
+ $progressBar: null,
+ /**
+ * The progress bar text (the %).
+ *
+ * @var {jQuery}
+ */
+ $progressText: null,
+
+ // Methods =================================================================================
+
+ /*
+ * Init.
+ */
+ init: function () {
+ this.$button = $( '#imagify-generate-webp-versions' );
+ this.$progressWrap = this.$button.siblings( '.imagify-progress' );
+ this.$progressBar = this.$progressWrap.find( '.bar' );
+ this.$progressText = this.$progressBar.find( '.percent' );
+
+ // Enable/Disable the button when the "Convert to webp" checkbox is checked/unchecked.
+ $( '#imagify_convert_to_webp' )
+ .on( 'change.imagify init.imagify', { imagifyOptionsBulk: this }, this.toggleButton )
+ .trigger( 'init.imagify' );
+
+ // Launch optimization.
+ this.$button.on( 'click.imagify', { imagifyOptionsBulk: this }, this.maybeLaunchAllProcesses );
+
+ // Imagifybeat for optimization queue.
+ $( d )
+ .on( 'imagifybeat-send', { imagifyOptionsBulk: this }, this.addQueueImagifybeat )
+ .on( 'imagifybeat-tick', { imagifyOptionsBulk: this }, this.processQueueImagifybeat )
+ // Imagifybeat for requirements.
+ .on( 'imagifybeat-send', this.addRequirementsImagifybeat )
+ .on( 'imagifybeat-tick', { imagifyOptionsBulk: this }, this.processRequirementsImagifybeat );
+ },
+
+ // Event callbacks =========================================================================
+
+ /**
+ * Enable/Disable the button when the "Convert to webp" checkbox is checked/unchecked.
+ *
+ * @param {object} e Event object.
+ */
+ toggleButton: function ( e ) {
+ if ( ! this.checked ) {
+ e.data.imagifyOptionsBulk.$button.attr( 'disabled', 'disabled' );
+ } else {
+ e.data.imagifyOptionsBulk.$button.removeAttr( 'disabled' );
+ }
+ },
+
+ /*
+ * Build the queue and launch all processes.
+ *
+ * @param {object} e Event object.
+ */
+ maybeLaunchAllProcesses: function ( e ) {
+ if ( ! e.data.imagifyOptionsBulk || e.data.imagifyOptionsBulk.working ) {
+ return;
+ }
+
+ if ( e.data.imagifyOptionsBulk.hasBlockingError( true ) ) {
+ return;
+ }
+
+ // Reset properties.
+ e.data.imagifyOptionsBulk.fetchQueue = imagifyOptions.bulk.contexts.slice();
+ e.data.imagifyOptionsBulk.queue = [];
+ e.data.imagifyOptionsBulk.processingQueue = [];
+ e.data.imagifyOptionsBulk.fetchError = false;
+ e.data.imagifyOptionsBulk.error = false;
+ e.data.imagifyOptionsBulk.working = true;
+ e.data.imagifyOptionsBulk.processIsStopped = false;
+ e.data.imagifyOptionsBulk.processedMedia = 0;
+ e.data.imagifyOptionsBulk.totalMedia = 0;
+
+ // Disable the button.
+ e.data.imagifyOptionsBulk.$button.attr( 'disabled', 'disabled' ).find( '.dashicons' ).addClass( 'rotate' );
+
+ // Add a message to be displayed when the user wants to quit the page.
+ $( w ).on( 'beforeunload.imagify', e.data.imagifyOptionsBulk.getConfirmMessage );
+
+ // Fasten Imagifybeat: 1 tick every 15 seconds, and disable suspend.
+ w.imagify.beat.interval( 15 );
+ w.imagify.beat.disableSuspend();
+
+ // Fetch IDs of media to optimize.
+ e.data.imagifyOptionsBulk.fetchIDs();
+ },
+
+ /*
+ * Get the message displayed to the user when (s)he leaves the page.
+ *
+ * @return {string}
+ */
+ getConfirmMessage: function () {
+ return imagifyOptions.bulk.labels.processing;
+ },
+
+ // Imagifybeat =============================================================================
+
+ /**
+ * Add a Imagifybeat ID on "imagifybeat-send" event to sync the optimization queue.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ addQueueImagifybeat: function ( e, data ) {
+ if ( e.data.imagifyOptionsBulk && e.data.imagifyOptionsBulk.processingQueue.length ) {
+ data[ imagifyOptions.bulk.imagifybeatIDs.queue ] = e.data.imagifyOptionsBulk.processingQueue;
+ }
+ },
+
+ /**
+ * Listen for the custom event "imagifybeat-tick" on $(document).
+ * It allows to update various data periodically.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ processQueueImagifybeat: function ( e, data ) {
+ if ( e.data.imagifyOptionsBulk && typeof data[ imagifyOptions.bulk.imagifybeatIDs.queue ] !== 'undefined' ) {
+ $.each( data[ imagifyOptions.bulk.imagifybeatIDs.queue ], function ( i, mediaData ) {
+ e.data.imagifyOptionsBulk.mediaProcessed( mediaData );
+ } );
+ }
+ },
+
+ /**
+ * Add a Imagifybeat ID for requirements on "imagifybeat-send" event.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ addRequirementsImagifybeat: function ( e, data ) {
+ data[ imagifyOptions.bulk.imagifybeatIDs.requirements ] = 1;
+ },
+
+ /**
+ * Listen for the custom event "imagifybeat-tick" on $(document).
+ * It allows to update requirements status periodically.
+ *
+ * @param {object} e Event object.
+ * @param {object} data Object containing all Imagifybeat IDs.
+ */
+ processRequirementsImagifybeat: function ( e, data ) {
+ if ( e.data.imagifyOptionsBulk && typeof data[ imagifyOptions.bulk.imagifybeatIDs.requirements ] === 'undefined' ) {
+ return;
+ }
+
+ data = data[ imagifyOptions.bulk.imagifybeatIDs.requirements ];
+
+ imagifyOptions.bulk.curlMissing = data.curl_missing;
+ imagifyOptions.bulk.editorMissing = data.editor_missing;
+ imagifyOptions.bulk.extHttpBlocked = data.external_http_blocked;
+ imagifyOptions.bulk.apiDown = data.api_down;
+ imagifyOptions.bulk.keyIsValid = data.key_is_valid;
+ imagifyOptions.bulk.isOverQuota = data.is_over_quota;
+ },
+
+ // Optimization ============================================================================
+
+ /*
+ * Fetch IDs of media to optimize.
+ */
+ fetchIDs: function () {
+ var _this, context;
+
+ if ( this.processIsStopped ) {
+ return;
+ }
+
+ if ( ! this.fetchQueue.length ) {
+ // No more IDs to fetch.
+ if ( this.queue.length ) {
+ // We have files to process.
+ // Reset and display the progress bar.
+ this.$progressBar.removeAttr( 'style' );
+ this.$progressText.text( '0' + ( this.totalMedia ? '/' + this.totalMedia : '' ) );
+ this.$progressWrap.slideDown().attr( 'aria-hidden', 'false' );
+
+ this.processQueue();
+ return;
+ }
+
+ if ( ! this.fetchError ) {
+ // No files to process.
+ this.fetchError = 'no-images';
+ }
+
+ // Error, or no files to process.
+ this.stopProcess( this.fetchError );
+ this.fetchError = false;
+ return;
+ }
+
+ // Fetch IDs for the next context.
+ _this = this;
+ context = this.fetchQueue.shift();
+
+ $.get( this.getAjaxUrl( 'getMediaIds', context ) )
+ .done( function( response ) {
+ var errorMessage;
+
+ if ( _this.processIsStopped ) {
+ return;
+ }
+
+ if ( response.data && response.data.message ) {
+ errorMessage = response.data.message;
+ } else {
+ errorMessage = imagifyOptions.bulk.ajaxErrorText;
+ }
+
+ if ( ! response.success ) {
+ // Error.
+ if ( ! _this.fetchError ) {
+ _this.fetchError = errorMessage;
+ }
+ return;
+ }
+
+ if ( ! $.isArray( response.data ) ) {
+ // Error: should be an array.
+ if ( ! _this.fetchError ) {
+ _this.fetchError = errorMessage;
+ }
+ return;
+ }
+
+ if ( ! response.data.length ) {
+ // No media to process.
+ return;
+ }
+
+ // Success.
+ _this.totalMedia += response.data.length;
+ _this.queue.push( {
+ context: context,
+ optimizeURL: _this.getAjaxUrl( 'bulkProcess', context ),
+ mediaIDs: response.data
+ } );
+ } )
+ .fail( function() {
+ // Error.
+ if ( ! _this.fetchError ) {
+ _this.fetchError = 'get-unoptimized-images';
+ }
+ } )
+ .always( function() {
+ // Fetch IDs for the next context.
+ _this.fetchIDs();
+ } );
+ },
+
+ /*
+ * Fill the processing queue until the buffer size is reached.
+ */
+ processQueue: function () {
+ var _this = this;
+
+ if ( this.processIsStopped ) {
+ return;
+ }
+
+ if ( ! this.queue.length && ! this.processingQueue.length ) {
+ return;
+ }
+
+ // Optimize the files.
+ $.each( this.queue, function ( i, item ) {
+ if ( _this.processingQueue.length >= imagifyOptions.bulk.bufferSize ) {
+ return false;
+ }
+
+ $.each( item.mediaIDs, function () {
+ _this.processMedia( {
+ context: item.context,
+ mediaID: item.mediaIDs.shift(),
+ optimizeURL: item.optimizeURL
+ } );
+
+ if ( ! item.mediaIDs.length ) {
+ _this.queue.shift();
+ }
+
+ if ( _this.processingQueue.length >= imagifyOptions.bulk.bufferSize ) {
+ return false;
+ }
+ } );
+ } );
+ },
+
+ /*
+ * Process the next media.
+ *
+ * @param {object} item {
+ * @type {string} context The context, like 'wp'.
+ * @type {int} mediaID The media ID.
+ * @type {string} optimizeURL The URL to ping to optimize the media.
+ * }
+ */
+ processMedia: function ( item ) {
+ var _this = this,
+ defaultResponse = {
+ context: item.context,
+ mediaID: item.mediaID
+ };
+
+ this.processingQueue.push( {
+ context: item.context,
+ mediaID: item.mediaID
+ } );
+
+ $.post( {
+ url: item.optimizeURL,
+ data: {
+ media_id: item.mediaID,
+ context: item.context
+ },
+ dataType: 'json'
+ } )
+ .done( function( response ) {
+ if ( response.success ) {
+ // Processing.
+ return;
+ }
+
+ // Error.
+ _this.mediaProcessed( defaultResponse );
+ } )
+ .fail( function() {
+ // Error.
+ _this.mediaProcessed( defaultResponse );
+ } );
+ },
+
+ /**
+ * After a media has been processed.
+ *
+ * @param {object} response {
+ * The response:
+ *
+ * @type {int} mediaID The media ID.
+ * @type {string} context The context.
+ * }
+ */
+ mediaProcessed: function( response ) {
+ var _this = this;
+
+ if ( this.processIsStopped ) {
+ return;
+ }
+
+ // Remove this media from the "being processed" list.
+ $.each( this.processingQueue, function( i, mediaData ) {
+ if ( response.context === mediaData.context && response.mediaID === mediaData.mediaID ) {
+ _this.processingQueue.splice( i, 1 );
+ return false;
+ }
+ } );
+
+ ++this.processedMedia;
+
+ // Update the progress bar.
+ response.progress = Math.floor( this.processedMedia / this.totalMedia * 100 );
+ this.$progressBar.css( 'width', response.progress + '%' );
+ this.$progressText.text( this.processedMedia + '/' + this.totalMedia );
+
+ if ( this.queue.length || this.processingQueue.length ) {
+ this.processQueue();
+ } else if ( this.totalMedia === this.processedMedia ) {
+ this.queueEmpty();
+ }
+ },
+
+ /*
+ * End.
+ */
+ queueEmpty: function () {
+ var errorArgs = {};
+
+ // Maybe display an error.
+ if ( false !== this.error ) {
+ if ( 'invalid-api-key' === this.error ) {
+ errorArgs = {
+ title: imagifyOptions.bulk.labels.invalidAPIKeyTitle,
+ type: 'info'
+ };
+ }
+ else if ( 'over-quota' === this.error ) {
+ errorArgs = {
+ title: imagifyOptions.bulk.labels.overQuotaTitle,
+ html: $( '#tmpl-imagify-overquota-alert' ).html(),
+ type: 'info',
+ customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
+ showConfirmButton: false
+ };
+ }
+ else if ( 'get-unoptimized-images' === this.error || 'consumed-all-data' === this.error ) {
+ errorArgs = {
+ title: imagifyOptions.bulk.labels.getUnoptimizedImagesErrorTitle,
+ html: imagifyOptions.bulk.labels.getUnoptimizedImagesErrorText,
+ type: 'info'
+ };
+ }
+ else if ( 'no-images' === this.error ) {
+ errorArgs = {
+ title: imagifyOptions.bulk.labels.nothingToDoTitle,
+ html: imagifyOptions.bulk.labels.nothingToDoText,
+ type: 'info'
+ };
+ }
+ else if ( 'no-backup' === this.error ) {
+ errorArgs = {
+ title: imagifyOptions.bulk.labels.nothingToDoTitle,
+ html: imagifyOptions.bulk.labels.nothingToDoNoBackupText,
+ type: 'info'
+ };
+ } else {
+ errorArgs = {
+ title: imagifyOptions.bulk.labels.error,
+ html: this.error,
+ type: 'info'
+ };
+ }
+
+ this.displayError( errorArgs );
+
+ // Reset the error.
+ this.error = false;
+ }
+
+ // Reset.
+ this.fetchQueue = [];
+ this.queue = [];
+ this.processingQueue = [];
+ this.fetchError = false;
+ this.working = false;
+ this.processIsStopped = false;
+ this.processedMedia = 0;
+ this.totalMedia = 0;
+
+ // Reset Imagifybeat interval and enable suspend.
+ w.imagify.beat.resetInterval();
+ w.imagify.beat.enableSuspend();
+
+ // Unlink the message displayed when the user wants to quit the page.
+ $( w ).off( 'beforeunload.imagify', this.getConfirmMessage );
+
+ // Reset the progress bar.
+ this.$progressWrap.slideUp().attr( 'aria-hidden', 'true' );
+ this.$progressBar.removeAttr( 'style' );
+ this.$progressText.text( '0' );
+
+ // Enable the button.
+ this.$button.removeAttr( 'disabled' ).find( '.dashicons' ).removeClass( 'rotate' );
+ },
+
+ // Tools ===================================================================================
+
+ /*
+ * Tell if we have a blocking error. Can also display an error message in a swal.
+ *
+ * @param {bool} displayErrorMessage False to not display any error message.
+ * @return {bool}
+ */
+ hasBlockingError: function ( displayErrorMessage ) {
+ displayErrorMessage = undefined !== displayErrorMessage && displayErrorMessage;
+
+ if ( imagifyOptions.bulk.curlMissing ) {
+ if ( displayErrorMessage ) {
+ this.displayError( {
+ html: imagifyOptions.bulk.labels.curlMissing
+ } );
+ }
+ return true;
+ }
+
+ if ( imagifyOptions.bulk.editorMissing ) {
+ if ( displayErrorMessage ) {
+ this.displayError( {
+ html: imagifyOptions.bulk.labels.editorMissing
+ } );
+ }
+ return true;
+ }
+
+ if ( imagifyOptions.bulk.extHttpBlocked ) {
+ if ( displayErrorMessage ) {
+ this.displayError( {
+ html: imagifyOptions.bulk.labels.extHttpBlocked
+ } );
+ }
+ return true;
+ }
+
+ if ( imagifyOptions.bulk.apiDown ) {
+ if ( displayErrorMessage ) {
+ this.displayError( {
+ html: imagifyOptions.bulk.labels.apiDown
+ } );
+ }
+ return true;
+ }
+
+ if ( ! imagifyOptions.bulk.keyIsValid ) {
+ if ( displayErrorMessage ) {
+ this.displayError( {
+ title: imagifyOptions.bulk.labels.invalidAPIKeyTitle,
+ type: 'info'
+ } );
+ }
+ return true;
+ }
+
+ if ( imagifyOptions.bulk.isOverQuota ) {
+ if ( displayErrorMessage ) {
+ this.displayError( {
+ title: imagifyOptions.bulk.labels.overQuotaTitle,
+ html: $( '#tmpl-imagify-overquota-alert' ).html(),
+ type: 'info',
+ customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
+ showConfirmButton: false
+ } );
+ }
+ return true;
+ }
+
+ return false;
+ },
+
+ /*
+ * Display an error message in a modal.
+ *
+ * @param {string} title The modal title.
+ * @param {string} text The modal text.
+ * @param {object} args Other less common args.
+ */
+ displayError: function ( title, text, args ) {
+ var def = {
+ title: '',
+ html: '',
+ type: 'error',
+ customClass: '',
+ width: 620,
+ padding: 0,
+ showCloseButton: true,
+ showConfirmButton: true
+ };
+
+ if ( $.isPlainObject( title ) ) {
+ args = $.extend( {}, def, title );
+ } else {
+ args = args || {};
+ args = $.extend( {}, def, {
+ title: title || '',
+ html: text || ''
+ }, args );
+ }
+
+ args.title = args.title || imagifyOptions.bulk.labels.error;
+ args.customClass += ' imagify-sweet-alert';
+
+ swal( args ).catch( swal.noop );
+ },
+
+ /*
+ * Get the URL used for ajax requests.
+ *
+ * @param {string} action An ajax action, or part of it.
+ * @param {string} context The context.
+ * @return {string}
+ */
+ getAjaxUrl: function ( action, context ) {
+ var url;
+
+ url = ajaxurl + w.imagify.concat + '_wpnonce=' + imagifyOptions.bulk.ajaxNonce;
+ url += '&action=' + imagifyOptions.bulk.ajaxActions[ action ];
+ url += '&context=' + context;
+ url += '&imagify_action=generate_webp';
+
+ return url;
+ },
+
+ /*
+ * Stop everything and set an error.
+ *
+ * @param {string} errorId An error ID.
+ */
+ stopProcess: function ( errorId ) {
+ this.processIsStopped = true;
+
+ this.error = errorId;
+
+ this.queueEmpty();
+ }
+ };
+
+ w.imagify.optionsBulk.init();
+
+} )(window, document, jQuery);
+/* eslint-enable no-underscore-dangle, consistent-this */
+
+
+// "Select all" checkboxes =========================================================================
+(function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ var jqPropHookChecked = $.propHooks.checked;
+
+ // Force `.prop()` to trigger a `change` event.
+ $.propHooks.checked = {
+ set: function( elem, value, name ) {
+ var ret;
+
+ if ( undefined === jqPropHookChecked ) {
+ ret = ( elem[ name ] = value );
+ } else {
+ ret = jqPropHookChecked( elem, value, name );
+ }
+
+ $( elem ).trigger( 'change.imagify' );
+
+ return ret;
+ }
+ };
+
+ // Check all checkboxes.
+ $( '.imagify-select-all' ).on( 'click.imagify', function() {
+ var $_this = $(this),
+ action = $_this.data( 'action' ),
+ $btns = $_this.closest( '.imagify-select-all-buttons' ),
+ $group = $btns.prev( '.imagify-check-group' ),
+ inactive = 'imagify-is-inactive';
+
+ if ( $_this.hasClass( inactive ) ) {
+ return false;
+ }
+
+ $btns.find( '.imagify-select-all' ).removeClass( inactive ).attr( 'aria-disabled', 'false' );
+ $_this.addClass( inactive ).attr( 'aria-disabled', 'true' );
+
+ $group.find( '.imagify-row-check' )
+ .prop( 'checked', function() {
+ var $this = $( this );
+
+ if ( $this.is( ':hidden,:disabled' ) ) {
+ return false;
+ }
+
+ if ( action === 'select' ) {
+ return true;
+ }
+
+ return false;
+ } );
+
+ } );
+
+ // Change buttons status on checkboxes interation.
+ $( '.imagify-check-group .imagify-row-check' ).on( 'change.imagify', function() {
+ var $group = $( this ).closest( '.imagify-check-group' ),
+ $checks = $group.find( '.imagify-row-check' ),
+ could_be = $checks.filter( ':visible:enabled' ).length,
+ are_checked = $checks.filter( ':visible:enabled:checked' ).length,
+ $btns = $group.next( '.imagify-select-all-buttons' ),
+ inactive = 'imagify-is-inactive';
+
+ // Toggle status of "check all" buttons.
+ if ( are_checked === 0 ) {
+ $btns.find( '[data-action="unselect"]' ).addClass( inactive ).attr( 'aria-disabled', 'true' );
+ }
+ if ( are_checked === could_be ) {
+ $btns.find( '[data-action="select"]' ).addClass( inactive ).attr( 'aria-disabled', 'true' );
+ }
+ if ( are_checked !== could_be && are_checked > 0 ) {
+ $btns.find( '.imagify-select-all' ).removeClass( inactive ).attr( 'aria-disabled', 'false' );
+ }
+ } );
+
+} )(window, document, jQuery);
diff --git a/wp-content/plugins/imagify/assets/js/options.min.js b/wp-content/plugins/imagify/assets/js/options.min.js
new file mode 100644
index 00000000..1da171bb
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/options.min.js
@@ -0,0 +1 @@
+window.imagify=window.imagify||{},function(a,b,c,d){var e=!1,f=!1;a("#imagify-settings #api_key").on("blur.imagify",function(){var b=a(this),d=b.val();return""!==a.trim(d)&&(a("#check_api_key").val()===d?(a("#imagify-check-api-container").html(' '+imagifyOptions.labels.ValidApiKeyText),!1):(!0===e?f.abort():(a("#imagify-check-api-container").remove(),b.after(' '+imagifyOptions.labels.waitApiKeyCheckText+" ")),e=!0,void(f=a.get(ajaxurl+c.imagify.concat+"action=imagify_check_api_key_validity&api_key="+b.val()+"&imagifycheckapikeynonce="+a("#imagifycheckapikeynonce").val()).done(function(b){b.success?(a("#imagify-check-api-container").remove(),swal({title:imagifyOptions.labels.ApiKeyCheckSuccessTitle,html:imagifyOptions.labels.ApiKeyCheckSuccessText,type:"success",padding:0,customClass:"imagify-sweet-alert"}).then(function(){location.reload()})):a("#imagify-check-api-container").html(' '+b.data),e=!1}))))}),a(".imagify-options-line").css("cursor","pointer").on("click.imagify",function(b){"INPUT"!==b.target.nodeName&&a('input[aria-describedby="'+a(this).attr("id")+'"]').trigger("click.imagify")}),a(".imagify-settings th span").on("click.imagify",function(){var b=a(this).parent().next("td").find(":checkbox");1===b.length&&b.trigger("click.imagify")}),a(".imagify-options-line").find("input").on("change.imagify focus.imagify",function(){var b;("checkbox"!==this.type||this.checked)&&(b=a(this).closest(".imagify-options-line").prev("label").prev(":checkbox"),b.length&&!b[0].checked&&b.prop("checked",!0))}),a('[name="imagify_settings[backup]"]').on("change.imagify",function(){var b=a(this),c=b.siblings("#backup-dir-is-writable"),d={action:"imagify_check_backup_dir_is_writable",_wpnonce:c.data("nonce")};if(b.is(":checked"))return void a.getJSON(ajaxurl,d).done(function(b){a.isPlainObject(b)&&b.success&&(b.data.is_writable?c.addClass("hidden"):c.removeClass("hidden"))});swal({title:imagifyOptions.labels.noBackupTitle,html:imagifyOptions.labels.noBackupText,type:"warning",customClass:"imagify-sweet-alert",padding:0,showCancelButton:!0,cancelButtonText:imagifySwal.labels.cancelButtonText,reverseButtons:!0}).then(function(){c.addClass("hidden")},function(){b.prop("checked",!0)})}),a('[name="imagify_settings[display_webp_method]"]').on("change.imagify init.imagify",function(b){"picture"===b.target.value?a(b.target).closest(".imagify-radio-group").next(".imagify-options-line").removeClass("imagify-faded"):a(b.target).closest(".imagify-radio-group").next(".imagify-options-line").addClass("imagify-faded")}).filter(":checked").trigger("init.imagify")}(jQuery,document,window),function(a,b,c,d){a.imagifyUser&&c.getJSON(ajaxurl,a.imagifyUser).done(function(a){c.isPlainObject(a)&&a.success&&(a.data.id=null,a.data.plan_id=null,a.data.is=[],c.each(a.data,function(b,d){var e=".imagify-user-"+b.replace(/_/g,"-");0===b.indexOf("is_")?d&&a.data.is.push(e):"is"!==b&&c(e).text(d)}),a.data.is.push("best-plan"),c(a.data.is.join(",")).removeClass("hidden"))})}(window,document,jQuery),function(a,b,c,d){function e(b){var d,e,f,g,h,i=!1,j=null;b&&(f=c("#imagify-custom-folders-selected"),g=f.find(".imagify-custom-folder-line"),h=g.find('[value="'+b+'"]'),h.length||(b=b.split("#///#"),d=b[1].replace(/\/+$/,"").toLowerCase(),e=a.imagify.template("imagify-custom-folder"),g.each(function(){var a=c(this),h=a.data("path").replace(/\/+$/,"").toLowerCase();return""!==h&&0===d.indexOf(h)?(i=!0,!1):d'+imagifyOptions.labels.filesTreeSubTitle+' '+imagifyOptions.labels.cleaningInfo+'
",type:"",customClass:"imagify-sweet-alert imagify-swal-has-subtitle imagify-folders-selection",showCancelButton:!0,padding:0,confirmButtonText:imagifyOptions.labels.confirmFilesTreeBtn,cancelButtonText:imagifySwal.labels.cancelButtonText,reverseButtons:!0}).then(function(){var a=c("#imagify-folders-tree input").serializeArray();a.length&&c.each(a,function(a,b){e(b.value)})}).catch(swal.noop)}).fail(function(){swal({title:imagifyOptions.labels.error,type:"error",customClass:"imagify-sweet-alert",padding:0})}).always(function(){b.removeAttr("disabled").next("img").attr("aria-hidden","true")}))}),c(b).on("click.imagify","#imagify-folders-tree [data-folder]",function(){var a=c(this),b=a.nextAll(".imagify-folders-sub-tree"),d=[];if(!a.attr("disabled")&&!a.siblings(":checkbox").is(":checked")){if(a.attr("disabled","disabled").addClass("imagify-loading"),b.length)return a.hasClass("imagify-is-open")?(b.addClass("hidden"),a.removeClass(" imagify-is-open")):(b.removeClass("hidden"),a.addClass("imagify-is-open")),void a.removeAttr("disabled").removeClass("imagify-loading");c("#imagify-custom-folders-selected").find("input").each(function(){d.push(this.value)}),c.post(imagifyOptions.getFilesTree,{folder:a.data("folder"),selected:d},null,"json").done(function(b){if(!b.success)return void swal({title:imagifyOptions.labels.error,html:b.data||"",type:"error",padding:0,customClass:"imagify-sweet-alert"});a.addClass("imagify-is-open").parent().append('")}).fail(function(){swal({title:imagifyOptions.labels.error,type:"error",padding:0,customClass:"imagify-sweet-alert"})}).always(function(){a.removeAttr("disabled").removeClass("imagify-loading")})}}),c("#imagify-custom-folders").on("click.imagify",".imagify-custom-folders-remove",function(){var b=c(this).closest(".imagify-custom-folder-line").addClass("imagify-will-remove");a.setTimeout(function(){b.remove(),c("#imagify-custom-folders-selected").siblings(".imagify-success.hidden").removeClass("hidden")},750)}),c("#imagify-add-themes-to-custom-folder").on("click.imagify",function(){var a=c(this);e(a.data("theme")),e(a.data("theme-parent")),a.replaceWith(""+imagifyOptions.labels.themesAdded+"
")}))}(window,document,jQuery),function(a,b,c,d){imagifyOptions.bulk&&(a.imagify.optionsBulk={fetchQueue:[],queue:[],processingQueue:[],fetchError:!1,error:!1,working:!1,processIsStopped:!0,processedMedia:0,totalMedia:0,$button:null,$progressWrap:null,$progressBar:null,$progressText:null,init:function(){this.$button=c("#imagify-generate-webp-versions"),this.$progressWrap=this.$button.siblings(".imagify-progress"),this.$progressBar=this.$progressWrap.find(".bar"),this.$progressText=this.$progressBar.find(".percent"),c("#imagify_convert_to_webp").on("change.imagify init.imagify",{imagifyOptionsBulk:this},this.toggleButton).trigger("init.imagify"),this.$button.on("click.imagify",{imagifyOptionsBulk:this},this.maybeLaunchAllProcesses),c(b).on("imagifybeat-send",{imagifyOptionsBulk:this},this.addQueueImagifybeat).on("imagifybeat-tick",{imagifyOptionsBulk:this},this.processQueueImagifybeat).on("imagifybeat-send",this.addRequirementsImagifybeat).on("imagifybeat-tick",{imagifyOptionsBulk:this},this.processRequirementsImagifybeat)},toggleButton:function(a){this.checked?a.data.imagifyOptionsBulk.$button.removeAttr("disabled"):a.data.imagifyOptionsBulk.$button.attr("disabled","disabled")},maybeLaunchAllProcesses:function(b){b.data.imagifyOptionsBulk&&!b.data.imagifyOptionsBulk.working&&(b.data.imagifyOptionsBulk.hasBlockingError(!0)||(b.data.imagifyOptionsBulk.fetchQueue=imagifyOptions.bulk.contexts.slice(),b.data.imagifyOptionsBulk.queue=[],b.data.imagifyOptionsBulk.processingQueue=[],b.data.imagifyOptionsBulk.fetchError=!1,b.data.imagifyOptionsBulk.error=!1,b.data.imagifyOptionsBulk.working=!0,b.data.imagifyOptionsBulk.processIsStopped=!1,b.data.imagifyOptionsBulk.processedMedia=0,b.data.imagifyOptionsBulk.totalMedia=0,b.data.imagifyOptionsBulk.$button.attr("disabled","disabled").find(".dashicons").addClass("rotate"),c(a).on("beforeunload.imagify",b.data.imagifyOptionsBulk.getConfirmMessage),a.imagify.beat.interval(15),a.imagify.beat.disableSuspend(),b.data.imagifyOptionsBulk.fetchIDs()))},getConfirmMessage:function(){return imagifyOptions.bulk.labels.processing},addQueueImagifybeat:function(a,b){a.data.imagifyOptionsBulk&&a.data.imagifyOptionsBulk.processingQueue.length&&(b[imagifyOptions.bulk.imagifybeatIDs.queue]=a.data.imagifyOptionsBulk.processingQueue)},processQueueImagifybeat:function(a,b){a.data.imagifyOptionsBulk&&void 0!==b[imagifyOptions.bulk.imagifybeatIDs.queue]&&c.each(b[imagifyOptions.bulk.imagifybeatIDs.queue],function(b,c){a.data.imagifyOptionsBulk.mediaProcessed(c)})},addRequirementsImagifybeat:function(a,b){b[imagifyOptions.bulk.imagifybeatIDs.requirements]=1},processRequirementsImagifybeat:function(a,b){a.data.imagifyOptionsBulk&&void 0===b[imagifyOptions.bulk.imagifybeatIDs.requirements]||(b=b[imagifyOptions.bulk.imagifybeatIDs.requirements],imagifyOptions.bulk.curlMissing=b.curl_missing,imagifyOptions.bulk.editorMissing=b.editor_missing,imagifyOptions.bulk.extHttpBlocked=b.external_http_blocked,imagifyOptions.bulk.apiDown=b.api_down,imagifyOptions.bulk.keyIsValid=b.key_is_valid,imagifyOptions.bulk.isOverQuota=b.is_over_quota)},fetchIDs:function(){var a,b;if(!this.processIsStopped){if(!this.fetchQueue.length)return this.queue.length?(this.$progressBar.removeAttr("style"),this.$progressText.text("0"+(this.totalMedia?"/"+this.totalMedia:"")),this.$progressWrap.slideDown().attr("aria-hidden","false"),void this.processQueue()):(this.fetchError||(this.fetchError="no-images"),this.stopProcess(this.fetchError),void(this.fetchError=!1));a=this,b=this.fetchQueue.shift(),c.get(this.getAjaxUrl("getMediaIds",b)).done(function(d){var e;if(!a.processIsStopped)return e=d.data&&d.data.message?d.data.message:imagifyOptions.bulk.ajaxErrorText,d.success&&c.isArray(d.data)?void(d.data.length&&(a.totalMedia+=d.data.length,a.queue.push({context:b,optimizeURL:a.getAjaxUrl("bulkProcess",b),mediaIDs:d.data}))):void(a.fetchError||(a.fetchError=e))}).fail(function(){a.fetchError||(a.fetchError="get-unoptimized-images")}).always(function(){a.fetchIDs()})}},processQueue:function(){var a=this;this.processIsStopped||(this.queue.length||this.processingQueue.length)&&c.each(this.queue,function(b,d){if(a.processingQueue.length>=imagifyOptions.bulk.bufferSize)return!1;c.each(d.mediaIDs,function(){if(a.processMedia({context:d.context,mediaID:d.mediaIDs.shift(),optimizeURL:d.optimizeURL}),d.mediaIDs.length||a.queue.shift(),a.processingQueue.length>=imagifyOptions.bulk.bufferSize)return!1})})},processMedia:function(a){var b=this,d={context:a.context,mediaID:a.mediaID};this.processingQueue.push({context:a.context,mediaID:a.mediaID}),c.post({url:a.optimizeURL,data:{media_id:a.mediaID,context:a.context},dataType:"json"}).done(function(a){a.success||b.mediaProcessed(d)}).fail(function(){b.mediaProcessed(d)})},mediaProcessed:function(a){var b=this;this.processIsStopped||(c.each(this.processingQueue,function(c,d){if(a.context===d.context&&a.mediaID===d.mediaID)return b.processingQueue.splice(c,1),!1}),++this.processedMedia,a.progress=Math.floor(this.processedMedia/this.totalMedia*100),this.$progressBar.css("width",a.progress+"%"),this.$progressText.text(this.processedMedia+"/"+this.totalMedia),this.queue.length||this.processingQueue.length?this.processQueue():this.totalMedia===this.processedMedia&&this.queueEmpty())},queueEmpty:function(){var b={};!1!==this.error&&(b="invalid-api-key"===this.error?{title:imagifyOptions.bulk.labels.invalidAPIKeyTitle,type:"info"}:"over-quota"===this.error?{title:imagifyOptions.bulk.labels.overQuotaTitle,html:c("#tmpl-imagify-overquota-alert").html(),type:"info",customClass:"imagify-swal-has-subtitle imagify-swal-error-header",showConfirmButton:!1}:"get-unoptimized-images"===this.error||"consumed-all-data"===this.error?{title:imagifyOptions.bulk.labels.getUnoptimizedImagesErrorTitle,html:imagifyOptions.bulk.labels.getUnoptimizedImagesErrorText,type:"info"}:"no-images"===this.error?{title:imagifyOptions.bulk.labels.nothingToDoTitle,html:imagifyOptions.bulk.labels.nothingToDoText,type:"info"}:"no-backup"===this.error?{title:imagifyOptions.bulk.labels.nothingToDoTitle,html:imagifyOptions.bulk.labels.nothingToDoNoBackupText,type:"info"}:{title:imagifyOptions.bulk.labels.error,html:this.error,type:"info"},this.displayError(b),this.error=!1),this.fetchQueue=[],this.queue=[],this.processingQueue=[],this.fetchError=!1,this.working=!1,this.processIsStopped=!1,this.processedMedia=0,this.totalMedia=0,a.imagify.beat.resetInterval(),a.imagify.beat.enableSuspend(),c(a).off("beforeunload.imagify",this.getConfirmMessage),this.$progressWrap.slideUp().attr("aria-hidden","true"),this.$progressBar.removeAttr("style"),this.$progressText.text("0"),this.$button.removeAttr("disabled").find(".dashicons").removeClass("rotate")},hasBlockingError:function(a){return a=void 0!==a&&a,imagifyOptions.bulk.curlMissing?(a&&this.displayError({html:imagifyOptions.bulk.labels.curlMissing}),!0):imagifyOptions.bulk.editorMissing?(a&&this.displayError({html:imagifyOptions.bulk.labels.editorMissing}),!0):imagifyOptions.bulk.extHttpBlocked?(a&&this.displayError({html:imagifyOptions.bulk.labels.extHttpBlocked}),!0):imagifyOptions.bulk.apiDown?(a&&this.displayError({html:imagifyOptions.bulk.labels.apiDown}),!0):imagifyOptions.bulk.keyIsValid?!!imagifyOptions.bulk.isOverQuota&&(a&&this.displayError({title:imagifyOptions.bulk.labels.overQuotaTitle,html:c("#tmpl-imagify-overquota-alert").html(),type:"info",customClass:"imagify-swal-has-subtitle imagify-swal-error-header",showConfirmButton:!1}),!0):(a&&this.displayError({title:imagifyOptions.bulk.labels.invalidAPIKeyTitle,type:"info"}),!0)},displayError:function(a,b,d){var e={title:"",html:"",type:"error",customClass:"",width:620,padding:0,showCloseButton:!0,showConfirmButton:!0};c.isPlainObject(a)?d=c.extend({},e,a):(d=d||{},d=c.extend({},e,{title:a||"",html:b||""},d)),d.title=d.title||imagifyOptions.bulk.labels.error,d.customClass+=" imagify-sweet-alert",swal(d).catch(swal.noop)},getAjaxUrl:function(b,c){var d;return d=ajaxurl+a.imagify.concat+"_wpnonce="+imagifyOptions.bulk.ajaxNonce,d+="&action="+imagifyOptions.bulk.ajaxActions[b],d+="&context="+c,d+="&imagify_action=generate_webp"},stopProcess:function(a){this.processIsStopped=!0,this.error=a,this.queueEmpty()}},a.imagify.optionsBulk.init())}(window,document,jQuery),function(a,b,c,d){var e=c.propHooks.checked;c.propHooks.checked={set:function(a,b,d){var f;return f=void 0===e?a[d]=b:e(a,b,d),c(a).trigger("change.imagify"),f}},c(".imagify-select-all").on("click.imagify",function(){var a=c(this),b=a.data("action"),d=a.closest(".imagify-select-all-buttons"),e=d.prev(".imagify-check-group"),f="imagify-is-inactive";if(a.hasClass(f))return!1;d.find(".imagify-select-all").removeClass(f).attr("aria-disabled","false"),a.addClass(f).attr("aria-disabled","true"),e.find(".imagify-row-check").prop("checked",function(){return!c(this).is(":hidden,:disabled")&&"select"===b})}),c(".imagify-check-group .imagify-row-check").on("change.imagify",function(){var a=c(this).closest(".imagify-check-group"),b=a.find(".imagify-row-check"),d=b.filter(":visible:enabled").length,e=b.filter(":visible:enabled:checked").length,f=a.next(".imagify-select-all-buttons"),g="imagify-is-inactive";0===e&&f.find('[data-action="unselect"]').addClass(g).attr("aria-disabled","true"),e===d&&f.find('[data-action="select"]').addClass(g).attr("aria-disabled","true"),e!==d&&e>0&&f.find(".imagify-select-all").removeClass(g).attr("aria-disabled","false")})}(window,document,jQuery);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/pricing-modal.js b/wp-content/plugins/imagify/assets/js/pricing-modal.js
new file mode 100644
index 00000000..2093d696
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/pricing-modal.js
@@ -0,0 +1,1203 @@
+// Imagify tabs ====================================================================================
+(function ($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ /**
+ * @Markup:
+ * ul.imagify-tabs
+ * li.imagify-tab.imagify-current
+ * a[href="#target"]
+ * div.imagify-tabs-contents
+ * div.imagify-tab-content#target
+ */
+ $(d).on('click.imagify', '.imagify-tab', function (e) {
+ var $_this = $(this),
+ curr_class = 'imagify-current',
+ target;
+
+ e.preventDefault();
+
+ if ($_this.hasClass('imagify-current')) {
+ return;
+ }
+
+ target = $_this.find('a').attr('href') || '#' + $_this.find('a').attr('aria-controls');
+
+ // Show right tab content.
+ $_this.closest('.imagify-tabs').next('.imagify-tabs-contents').find('.imagify-tab-content').hide().attr('aria-hidden', 'true');
+ $(target).fadeIn(275).attr('aria-hidden', 'false');
+
+ // Change active tabs.
+ $_this.closest('.imagify-tabs').find('.imagify-tab').removeClass(curr_class).attr('aria-selected', 'false');
+ $_this.addClass(curr_class).attr('aria-selected', 'true');
+ });
+
+})(jQuery, document, window);
+
+
+// Imagify payment modal ===========================================================================
+(function ($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
+
+ /**
+ * Payment Modal.
+ *
+ * @since 1.6
+ * @since 1.6.3 include discount campaign
+ * @author Geoffrey
+ */
+ var imagifyModal = {};
+
+ if (! $('#imagify-pricing-modal').length) {
+ return;
+ }
+
+ imagifyModal = {
+ $modal: $('#imagify-pricing-modal'),
+ $checkboxes: $('.imagify-offer-line .imagify-checkbox'),
+ $radios: $('.imagify-payment-modal .imagify-radio-line input'),
+ // Plans selection view & payment process view hidden by default.
+ $preView: $('#imagify-pre-checkout-view'),
+ $plansView: $('#imagify-plans-selection-view').hide(),
+ $paymentView: $('#imagify-payment-process-view').hide(),
+ $successView: $('#imagify-success-view').hide(),
+ $anotherBtn: $('.imagify-choose-another-plan'),
+ speedFadeIn: 300,
+
+ getHtmlPrice: function (content, period) {
+ var monthly, yearly, m, y, output;
+
+ if (! period) {
+ period = null;
+ }
+
+ if (typeof content !== 'object') {
+ content += ''; // Be sure content is a string.
+ content = content.split('.');
+ content[1] = content[1].length === 1 ? content[1] + '0' : ('' + content[1]).substring(0, 2);
+
+ output = '' + content[0] + ' ';
+ output += '.' + content[1] + ' ';
+
+ return output;
+ }
+
+ monthly = content.monthly + '';
+ yearly = content.yearly + '';
+ m = '0' === monthly ? ['0', '00'] : monthly.split('.');
+ y = '0' === yearly ? ['0', '00'] : yearly.split('.');
+ output = '';
+ /* eslint-disable indent */
+ output += '';
+ output += '' + m[0] + ' ';
+ output += '.' + (m[1].length === 1 ? m[1] + '0' : ('' + m[1]).substring(0, 2)) + ' ';
+ output += ' ';
+ output += '';
+ output += '' + y[0] + ' ';
+ output += '.' + (y[1].length === 1 ? y[1] + '0' : ('' + y[1]).substring(0, 2)) + ' ';
+ output += ' ';
+ /* eslint-enable indent */
+ output += ' ';
+
+ return output;
+ },
+
+ getHtmlDiscountPrice: function (content, period) {
+ var monthly, yearly,
+ output = '';
+
+ if (! period) {
+ period = null;
+ }
+
+ if (typeof content === 'object') {
+ monthly = content.monthly + '';
+ yearly = content.yearly + '';
+
+ output += '';
+ /* eslint-disable indent */
+ output += '$ ';
+ output += '';
+ output += '';
+ output += '' + monthly + ' ';
+ output += ' ';
+ output += '';
+ output += '' + yearly + ' ';
+ output += ' ';
+ output += ' ';
+ /* eslint-enable indent */
+ output += ' ';
+ } else {
+ content += ''; // Be sure content is a string.
+ output += '';
+ /* eslint-disable indent */
+ output += '$ ';
+ output += '' + content + ' ';
+ /* eslint-enable indent */
+ output += ' ';
+ }
+
+ return output;
+ },
+
+ /**
+ * @uses imagifyModal.getHtmlPrice()
+ * @uses imagifyModal.getHtmlDiscountPrice()
+ */
+ populateOffer: function ($offer, datas, type, classes) {
+ var promo = w.imagify_discount_datas,
+ add = datas.additional_gb, // 4 (monthly)
+ ann = datas.annual_cost, // 49.9 (monthly)
+ id = datas.id, // 3 (monthly/onetime)
+ lab = datas.label, // 'lite' (monthly/onetime)
+ mon = datas.monthly_cost, // 4.99 (monthly)
+ quo = datas.quota, // 1000 (MB) - 5000 images (monthly/onetime)
+ cos = datas.cost, // 3.49 (onetime)
+ name = -1 === quo ? 'Unlimited' : (quo >= 1000 ? quo / 1000 + ' GB' : quo + ' MB'),
+ pcs = 'monthly' === type ? { monthly: mon, yearly: Math.round(ann / 12 * 100) / 100 } : cos,
+ pcsd = pcs, // Used if discount is active.
+ percent, $datas_c, datas_content;
+
+ // Change pricing value only if discount in percentage is active and if offer is a monthly and not a onetime.
+ if (promo.is_active && 'percentage' === promo.coupon_type && 'monthly' === type) {
+ percent = (100 - promo.coupon_value) / 100;
+ pcs = 'monthly' === type ? {
+ monthly: mon * percent,
+ yearly: Math.round((ann * percent) / 12 * 100) / 100
+ } : cos * percent;
+ }
+
+ if (typeof classes !== 'undefined') {
+ $offer.addClass('imagify-' + type + '-' + lab + classes);
+ }
+
+ // Name.
+ $offer.find('.imagify-offer-size').text(name);
+
+ // Main prices (pcs can be an object or a string).
+ $offer.find('.imagify-number-block').html(imagifyModal.getHtmlPrice(pcs, 'monthly'));
+
+ // discount prices
+ if (promo.is_active && 'percentage' === promo.coupon_type && 'monthly' === type) {
+
+ $offer.find('.imagify-price-block').prev('.imagify-price-discount').remove();
+ $offer.find('.imagify-price-block').before(imagifyModal.getHtmlDiscountPrice(pcsd, 'monthly'));
+ }
+
+ // Nb images.
+ $offer.find('.imagify-approx-nb').text(quo * 5);
+
+ if ('monthly' === type) {
+ // Additionnal price.
+ $offer.find('.imagify-price-add-data').text('$' + add);
+ }
+
+ // Button data-offer attr.
+ $datas_c = $offer.find('.imagify-payment-btn-select-plan').length ? $offer.find('.imagify-payment-btn-select-plan') : $offer;
+
+ if ('monthly' === type) {
+ datas_content = '{"' + lab + '":{"id":' + id + ',"name":"' + name + '","data":' + quo + ',"dataf":"' + name + '","imgs":' + (quo * 5) + ',"prices":{"monthly":' + pcs.monthly + ',"yearly":' + pcs.yearly + ',"add":' + add + '}}}';
+ } else {
+ datas_content = '{"ot' + lab + '":{"id":' + id + ',"name":"' + name + '","data":' + quo + ',"dataf":"' + name + '","imgs":' + (quo * 5) + ',"price":' + pcs + '}}';
+ }
+
+ $datas_c.attr('data-offer', datas_content);
+
+ return $offer;
+ },
+
+ populatePayBtn: function () {
+ var $monthlyOffer = $('.imagify-offer-monthly'),
+ $onetimeOffer = $('.imagify-offer-onetime'),
+ pl_datas, ot_datas,
+ price = 0,
+ price_pl = 0,
+ price_ot = 0;
+
+ if ($monthlyOffer.length) {
+ pl_datas = JSON.parse($monthlyOffer.attr('data-offer'));
+ // Calculate price_pl only if that offer is selected.
+ if ($monthlyOffer.hasClass('imagify-offer-selected')) {
+ if ($('#imagify-subscription-monthly').filter(':checked').length) {
+ price_pl = pl_datas[Object.keys(pl_datas)[0]].prices.monthly;
+ } else {
+ price_pl = pl_datas[Object.keys(pl_datas)[0]].prices.yearly * 12;
+ }
+ }
+ }
+
+ if ($onetimeOffer.length) {
+ ot_datas = JSON.parse($onetimeOffer.attr('data-offer'));
+ // Calculate price_ot only if that offer is selected.
+ if ($onetimeOffer.hasClass('imagify-offer-selected')) {
+ price_ot = ot_datas[Object.keys(ot_datas)[0]].price;
+ }
+ }
+
+ // Calculate price.
+ price = parseFloat(price_ot + price_pl).toFixed(2);
+
+ // Edit button price.
+ //$( '.imagify-global-amount' ).text( price ); // Not used.
+
+ if ('0.00' === price || 0 === price) {
+ $('#imagify-modal-checkout-btn').attr('disabled', 'disabled').addClass('imagify-button-disabled');
+ } else {
+ $('#imagify-modal-checkout-btn').removeAttr('disabled').removeClass('imagify-button-disabled');
+ }
+ },
+
+ checkCoupon: function () {
+ var code = $('#imagify-coupon-code').val(),
+ $cptext, $label, $section, nonce;
+
+ if ('' === code) {
+ return;
+ }
+
+ $cptext = $('.imagify-coupon-text');
+ $label = $cptext.find('label');
+ $section = $('.imagify-coupon-section');
+ nonce = $('#imagify-get-pricing-modal').data('nonce');
+
+ $cptext.addClass('checking');
+
+ // Get the true prices.
+ $.post(ajaxurl, { action: 'imagify_check_coupon', coupon: code, imagifynonce: nonce }, function (response) {
+ var coupon_value;
+
+ $cptext.removeClass('checking');
+
+ // Error during the request.
+ if (! response.success) {
+ $section.removeClass('validated').addClass('invalid');
+ $label.text(imagifyPricingModal.labels.errorCouponAPI);
+ } else if (response.data.success) {
+ coupon_value = 'percentage' === response.data.coupon_type ? response.data.value + '%' : '$' + response.data.value;
+ $section.removeClass('invalid').addClass('validated');
+ $label.html(imagifyPricingModal.labels.successCouponAPI);
+ $label.find('.imagify-coupon-offer').text(coupon_value);
+ $label.find('.imagify-coupon-word').text(code);
+ } else {
+ $section.removeClass('validated').addClass('invalid');
+ $label.text(response.data.detail);
+ }
+ });
+ },
+
+ /**
+ * @uses imagifyModal.populateOffer()
+ * @uses imagifyModal.populatePayBtn()
+ * @uses imagifyModal.checkCoupon()
+ */
+ getPricing: function ($button) {
+ var nonce = $button.data('nonce'),
+ prices_rq_datas = {
+ action: 'imagify_get_prices',
+ imagifynonce: nonce
+ },
+ imgs_rq_datas = {
+ action: 'imagify_get_images_counts',
+ imagifynonce: nonce
+ },
+ prices_rq_discount = {
+ action: 'imagify_get_discount',
+ imagifynonce: nonce
+ };
+
+ imagifyModal.$modal.find('.imagify-modal-loader').hide().show();
+
+ /**
+ * TODO: change the way to waterfall requests.
+ * Use setInterval + counter instead.
+ */
+
+ // Get the true prices.
+ $.post(ajaxurl, prices_rq_datas, function (prices_response) {
+
+ if (! prices_response.success) {
+ // TODO: replace modal content by any information.
+ // An error occurred.
+
+ // Populate Pay button.
+ imagifyModal.populatePayBtn();
+ return;
+ }
+
+ // get the image estimates sizes
+ $.post(ajaxurl, imgs_rq_datas, function (imgs_response) {
+
+ if (! imgs_response.success) {
+ // TODO: replace modal content by any information.
+ // An error occurred.
+ return;
+ }
+
+ // Get the discount informations.
+ $.post(ajaxurl, prices_rq_discount, function (discount_response) {
+ var images_datas, prices_datas, promo_datas,
+ offers, consumption, suggested,
+ freeQuota = 25,
+ ot_html = '',
+ mo_html = '',
+ $mo_tpl, $ot_tpl,
+ ot_clone, mo_clone,
+ $estim_block, $offers_block,
+ $banners, date_end, promo, discount;
+
+ if (! discount_response.success) {
+ // TODO: replace modal content by any information.
+ // An error occurred.
+ return;
+ }
+
+ images_datas = imgs_response.data;
+ prices_datas = prices_response.data;
+ promo_datas = discount_response.data;
+ offers = {
+ mo: [],
+ ot: []
+ };
+ consumption = {
+ month: images_datas.average_month_size.raw / Math.pow(1024, 2), // Bytes to MB.
+ total: images_datas.total_library_size.raw / Math.pow(1024, 2) // Bytes to MB.
+ };
+ $mo_tpl = $('#imagify-offer-monthly-template');
+ $ot_tpl = $('#imagify-offer-onetime-template');
+ ot_clone = $ot_tpl.html();
+ mo_clone = $mo_tpl.html();
+ $estim_block = $('.imagify-estimation-block');
+
+ // Remove inactive offers.
+ $.each(prices_datas.monthlies, function (index, value) {
+ if ('undefined' === typeof value.active
+ ||
+ ('undefined' !== typeof value.active && true === value.active)
+ ) {
+ if ('free' === value.label) {
+ freeQuota = value.quota;
+ }
+ offers.mo.push(value);
+ }
+ });
+ $.each(prices_datas.onetimes, function (index, value) {
+ if ('undefined' === typeof value.active
+ ||
+ ('undefined' !== typeof value.active && true === value.active)
+ ) {
+ offers.ot.push(value);
+ }
+ });
+
+ // Refresh Analyzing block.
+ $estim_block.removeClass('imagify-analyzing');
+ $estim_block.find('.average-month-size').text(images_datas.average_month_size.human);
+ $estim_block.find('.total-library-size').text(images_datas.total_library_size.human);
+
+ // Switch offers title if < 25mb.
+ if (consumption.total + consumption.month < freeQuota) {
+ $('.imagify-pre-checkout-offers .imagify-modal-title').addClass('imagify-enough-free');
+ } else {
+ $('.imagify-enough-free').removeClass('imagify-enough-free');
+ }
+
+ // Reset offer selection.
+ $('.imagify-offer-selected').removeClass('imagify-offer-selected').find('.imagify-checkbox').prop('checked', false);
+
+ // Don't create prices table if something went wrong during request.
+ if (null === offers.mo || null === offers.ot) {
+ $offers_block = $('.imagify-pre-checkout-offers');
+
+ // Hide main content.
+ $offers_block.hide().attr('aria-hidden', true);
+
+ // Show error message.
+ $offers_block.closest('.imagify-modal-views').find('.imagify-popin-message').remove();
+ $offers_block.after('' + imagifyPricingModal.labels.errorPriceAPI + '
');
+
+ // Show the modal content.
+ imagifyModal.$modal.find('.imagify-modal-loader').fadeOut(300);
+ return;
+ }
+
+ // Autofill coupon code & Show banner if discount is active.
+ w.imagify_discount_datas = promo_datas;
+
+ if (promo_datas.is_active) {
+ $banners = $('.imagify-modal-promotion');
+ date_end = promo_datas.date_end.split('T')[0];
+ promo = promo_datas.coupon_value;
+ discount = 'percentage' === promo_datas.coupon_type ? promo + '%' : '$' + promo;
+
+ // Fill coupon code.
+ $('#imagify-coupon-code').val(promo_datas.label).attr('readonly', true);
+
+ // Show banners.
+ $banners.addClass('active').attr('aria-hidden', 'false');
+
+ // Populate banners.
+ $banners.find('.imagify-promotion-number').text(discount);
+ $banners.find('.imagify-promotion-date').text(date_end);
+
+ // Auto validate coupon.
+ imagifyModal.checkCoupon();
+ }
+
+ /**
+ * Find which plan(s) should be pre-selected.
+ */
+ suggested = imagifyModal.getSuggestedOffers(offers, consumption, freeQuota);
+
+ /**
+ * Below lines will build Plan and Onetime offers lists.
+ * It will also pre-select a Plan and/or Onetime in both of views: pre-checkout and pricing tables.
+ */
+ if (0 === offers.mo.length) {
+ $('.imagify-pre-checkout-offers .imagify-offer-monthly').remove();
+ $('.imagify-tabs').remove();
+ $('.imagify-pricing-tab-monthly').remove();
+ } else {
+ // Now, do the MONTHLIES Markup.
+ $.each(offers.mo, function (index, value) {
+ var $tpl, $offer,
+ classes = '';
+
+ // If offer is too big (far) than estimated needs, don't show the offer.
+ if ((index - suggested.mo.index) > 2) {
+ return true;
+ }
+
+ if (index === suggested.mo.index) {
+ // It's the one to display.
+ $offer = $('.imagify-pre-checkout-offers .imagify-offer-monthly');
+
+ if (suggested.mo.selected) {
+ classes = ' imagify-offer-selected';
+
+ // Add this offer as pre-selected item in pre-checkout view.
+ $offer.addClass('imagify-offer-selected').find('.imagify-checkbox').prop('checked', true);
+ }
+
+ // Populate the Pre-checkout view depending on user_cons.
+ imagifyModal.populateOffer($offer, value, 'monthly');
+ }
+
+ // Populate each offer.
+ $tpl = $(mo_clone).clone();
+ $tpl = imagifyModal.populateOffer($tpl, value, 'monthly', classes);
+
+ // Complete Monthlies HTML.
+ mo_html += $tpl[0].outerHTML;
+ });
+ }
+
+ if (0 === offers.ot.length) {
+ $('.imagify-pre-checkout-offers .imagify-offer-onetime').remove();
+ $('.imagify-tabs').remove();
+ $('.imagify-pricing-tab-onetime').remove();
+ } else {
+ // Do the ONETIMES Markup.
+ $.each(offers.ot, function (index, value) {
+ var $tpl, $offer,
+ classes = '';
+
+ // Parent classes.
+ if (index === suggested.ot.index) {
+ $offer = $('.imagify-pre-checkout-offers .imagify-offer-onetime');
+
+ if (suggested.ot.selected) {
+ classes = ' imagify-offer-selected';
+
+ // Add this offer as pre-selected item in pre-checkout view.
+ $offer.addClass('imagify-offer-selected').find('.imagify-checkbox').prop('checked', true);
+ }
+
+ // Populate the Pre-checkout view depending on user_cons.
+ imagifyModal.populateOffer($offer, value, 'onetime');
+ }
+
+ // Populate each offer.
+ $tpl = $(ot_clone).clone();
+ $tpl = imagifyModal.populateOffer($tpl, value, 'onetime', classes);
+
+ // complete Onetimes HTML
+ ot_html += $tpl[0].outerHTML;
+ });
+ }
+
+ // Fill pricing tables.
+ if ($mo_tpl.parent().find('.imagify-offer-line')) {
+ $mo_tpl.parent().find('.imagify-offer-line').remove();
+ }
+
+ $mo_tpl.before(mo_html);
+
+ if ($ot_tpl.parent().find('.imagify-offer-line')) {
+ $ot_tpl.parent().find('.imagify-offer-line').remove();
+ }
+
+ $ot_tpl.before(ot_html);
+
+ // Show the content.
+ imagifyModal.$modal.find('.imagify-modal-loader').fadeOut(300);
+
+ }); // Third AJAX request to get discount information.
+
+ }); // Second AJAX request for image estimation sizes.
+
+ // Populate Pay button.
+ imagifyModal.populatePayBtn();
+ }); // End $.post.
+ },
+
+ /**
+ * Tell which offer(s) should be pre-selected.
+ *
+ * @param object offers {
+ * An object build like this:
+ *
+ * @type array mo Monthly offers. The most important data being 'quota' (MBytes).
+ * @type array ot One-Time offers. The most important data being 'quota' (MBytes).
+ * }
+ * @param object consumption {
+ * An object build like this:
+ *
+ * @type array month Average image size uploaded each month on the site (MBytes).
+ * @type array total Total image size in the library (MBytes).
+ * }
+ * @param int freeQuota Quota of the free monthly offer (MBytes). Currently 25.
+ * @return object {
+ * An object build like this:
+ *
+ * @type object mo An object containing the index of the suggested monthly offer, and if it should be selected.
+ * @type object ot An object containing the index of the suggested one-time offer, and if it should be selected.
+ * }
+ */
+ getSuggestedOffers: function (offers, consumption, freeQuota) {
+ var tmpMB = consumption.total + consumption.month,
+ plan,
+ biggestPlan = {
+ quota: 0
+ },
+ unlimitedPlan,
+ suggested = {
+ mo: false,
+ ot: false
+ };
+
+ /**
+ * Paid monthly plan.
+ */
+ $.each(offers.mo, function (index, value) {
+ // if this is the unlimited plan, save it; we may need to come back to it.
+ if (0 > value.quota) {
+ unlimitedPlan = {
+ index: index,
+ selected: 1
+ };
+ }
+
+ // This is the biggest plan we've seen so far. Save it in case we need it later.
+ if (value.quota > biggestPlan.quota) {
+ biggestPlan = {
+ index: index,
+ selected: 1,
+ quota: value.quota
+ };
+ }
+
+ // Skip the free plan.
+ if ((0 === value.monthly_cost) && (0 === value.annual_cost)) {
+ return true;
+ }
+
+ // For all except unlimited plan: Rule out this plan if quota is less than either library size or monthly usage.
+ if ((0 >= value.quota) && (consumption.month > value.quota) || (consumption.total > value.quota)) {
+ return true;
+ }
+
+ // For all except unlimited plan: Consider this plan if its quota meets consumption needs.
+ if ((0 <= value.quota) && (consumption.month < value.quota) && (consumption.total < value.quota)) {
+ // Select this one when (1) we haven't yet selected a plan, or (2) when this plan is less quota than the current one.
+ if (('undefined' === typeof plan) || (plan.quota > value.quota)) {
+ plan = value;
+ suggested.mo = {
+ index: index,
+ selected: (freeQuota > consumption.month) && (freeQuota > consumption.total) ? 0 : 1
+ };
+ }
+
+ return true;
+ }
+
+ return true;
+ });
+
+ // If we don't have a plan selected by the time we get here, we need the infinite plan, or the biggest if infinite isn't available.
+ if (false === suggested.mo) {
+ if ('undefined' !== typeof unlimitedPlan) {
+ suggested.mo = unlimitedPlan;
+ } else {
+ suggested.mo = biggestPlan;
+ }
+ }
+ // Remaining MB.
+ tmpMB -= offers.mo[suggested.mo.index].quota;
+
+ // If we don't have active onetime plans, we're done.
+ if (0 === offers.ot.length) {
+ return suggested;
+ }
+
+ if (tmpMB <= 0) {
+ /**
+ * The monthly plan is big enough to optimize all the images that already are in the library.
+ * We'll display a One-Time plan that is able to optimize the whole library, in case the user doesn't want a monthly plan, but it won't be pre-selected.
+ */
+ $.each(offers.ot, function (index, value) {
+ if (value.quota < consumption.total) {
+ // This plan is not big enough for the user needs.
+ return true;
+ }
+
+ // Suggested monthly plan.
+ suggested.ot = {
+ index: index,
+ selected: 0
+ };
+ return false;
+ });
+
+ if (false === suggested.ot) {
+ suggested.ot = {
+ index: offers.ot.length - 1,
+ selected: 0
+ };
+ }
+
+ return suggested;
+ }
+
+ /**
+ * The monthly plan is not big enough to optimize all the images that already are in the library.
+ * We need to select a One-Time plan.
+ */
+ $.each(offers.ot, function (index, value) {
+ if (value.quota < tmpMB) {
+ // This plan is not big enough for the user needs.
+ return true;
+ }
+
+ // Suggested one-time plan.
+ suggested.ot = {
+ index: index,
+ selected: 1
+ };
+ return false;
+ });
+
+ if (false !== suggested.ot) {
+ // OK, we have all we need.
+ return suggested;
+ }
+
+ /**
+ * If nothing is selected, that means no OT plan is big enough for the user.
+ * In that case we fallback to the biggest available, and we need to increase the monthly plan.
+ */
+ suggested.ot = {
+ index: offers.ot.length - 1,
+ selected: 1
+ };
+
+ // Reset monthly plan.
+ suggested.mo = false;
+
+ // Reset the remaining MB and subtract the OT plan quota.
+ tmpMB = consumption.total + consumption.month - offers.ot[suggested.ot.index].quota;
+
+ // Search for a new monthly plan.
+ $.each(offers.mo, function (index, value) {
+ if (value.quota < tmpMB) {
+ // This plan is not big enough for the user needs.
+ return true;
+ }
+
+ // Suggested monthly plan.
+ suggested.mo = {
+ index: index,
+ selected: 1
+ };
+ return false;
+ });
+
+ if (false === suggested.mo) {
+ /**
+ * If nothing is selected, that means no monthly plan is big enough for the user's monthly consumption.
+ * In that case we fallback to the biggest available.
+ */
+ suggested.mo = {
+ index: offers.mo.length - 1,
+ selected: 1
+ };
+ }
+
+ return suggested;
+ },
+
+ /**
+ * @uses imagifyModal.populatePayBtn()
+ */
+ checkCheckbox: function ($checkbox) {
+ var sel_class = 'imagify-offer-selected';
+
+ $checkbox.each(function () {
+ var $this = $(this);
+
+ if ($this.is(':checked')) {
+ $this.closest('.imagify-offer-line').addClass(sel_class);
+ } else {
+ $this.closest('.imagify-offer-line').removeClass(sel_class);
+ }
+ });
+
+ // Update pay button.
+ imagifyModal.populatePayBtn();
+ },
+
+ /**
+ * @uses imagifyModal.populatePayBtn()
+ */
+ checkRadio: function ($radio) {
+ var year_class = 'imagify-year-selected',
+ month_class = 'imagify-month-selected';
+
+ $radio.each(function () {
+ // To handle modal pricing & modal suggestion.
+ var $_this = $(this),
+ $parent, $to_switch;
+
+ if ($_this.parent('.imagify-cart-list-switcher').length) {
+ $parent = $_this.closest('.imagify-cart');
+ } else if ($_this.parent('.imagify-small-options').length) {
+ $parent = $_this.parent('.imagify-small-options').next('.imagify-pricing-table');
+ } else {
+ $parent = $_this.closest('.imagify-offer-line');
+ }
+
+ $to_switch = $parent.find('.imagify-switch-my');
+
+ if ($_this.val() === 'yearly') {
+ $parent.addClass(year_class).removeClass(month_class);
+ $to_switch.find('.imagify-monthly').attr('aria-hidden', 'true');
+ $to_switch.find('.imagify-yearly').attr('aria-hidden', 'false');
+ } else {
+ $parent.addClass(month_class).removeClass(year_class);
+ $to_switch.find('.imagify-monthly').attr('aria-hidden', 'false');
+ $to_switch.find('.imagify-yearly').attr('aria-hidden', 'true');
+ }
+ });
+
+ // Update Pay button information.
+ imagifyModal.populatePayBtn();
+
+ return $radio;
+ },
+
+ /**
+ * Currently not used.
+ * @uses imagifyModal.populatePayBtn()
+ */
+ populateBtnPrice: setInterval(function () {
+ imagifyModal.populatePayBtn();
+ }, 1000),
+
+ /**
+ * 1) Modal Payment change/select plan
+ * 2) Checkout selection(s)
+ * 3) Payment process
+ */
+
+ getPeriod: function () {
+ return $('.imagify-offer-monthly').hasClass('imagify-month-selected') ? 'monthly' : 'yearly';
+ },
+
+ getApiKey: function () {
+ return $('#imagify-payment-iframe').data('imagify-api');
+ },
+
+ switchToView: function ($view, data) {
+ var viewId = $view.attr('id'),
+ $modalContent = imagifyModal.$modal.children('.imagify-modal-content');
+
+ $view.siblings('.imagify-modal-views').hide().attr('aria-hidden', 'true');
+
+ // Plans view has tabs: display the right one.
+ if (data && data.tab) {
+ $view.find('a[href="#' + data.tab + '"]').trigger('click.imagify');
+ }
+
+ // Payment view: it's an iframe.
+ if ('imagify-payment-process-view' === viewId) {
+ $modalContent.addClass('imagify-iframe-viewing');
+ } else {
+ $modalContent.removeClass('imagify-iframe-viewing');
+ }
+
+ // Success view: some tweaks.
+ if ('imagify-success-view' === viewId) {
+ $modalContent.addClass('imagify-success-viewing');
+ imagifyModal.$modal.attr('aria-labelledby', 'imagify-success-view');
+ } else {
+ $modalContent.removeClass('imagify-success-viewing');
+ imagifyModal.$modal.removeAttr('aria-labelledby');
+ }
+
+ $view.fadeIn(imagifyModal.speedFadeIn).attr('aria-hidden', 'false');
+ },
+
+ /**
+ * @uses imagifyModal.getApiKey()
+ */
+ iframeSetSrc: function (params) {
+ /**
+ * params = {
+ * 'monthly': {
+ * 'lite': {
+ * name: 'something',
+ * id: ''
+ * }
+ * },
+ * 'onetime': {
+ * 'recommended': {
+ * name: 'Recommend',
+ * id: ''
+ * }
+ * },
+ * 'period': 'monthly'|'yearly'
+ * }
+ */
+
+ var $iframe = $('#imagify-payment-iframe'),
+ iframe_src = $iframe.attr('src'),
+ pay_src = $iframe.data('src'),
+ monthly_id = 0,
+ onetime_id = 0,
+ // Stop it ESLint, you're drunk.
+ key, amount, // eslint-disable-line no-unused-vars
+ rt_onetime, rt_yearly, rt_monthly, coupon, rt_coupon, $iframeClone, tofind;
+
+ // If we only change monthly/yearly payment mode.
+ if (typeof params === 'string' && '' !== iframe_src) {
+ tofind = 'monthly' === params ? 'yearly' : 'monthly';
+ iframe_src = iframe_src.replace(tofind, params);
+ $iframe.attr('src', iframe_src);
+ return;
+ }
+
+ // If we get new informations about products.
+ if (typeof params !== 'object') {
+ return;
+ }
+
+ if (params.monthly) {
+ monthly_id = params.monthly[Object.keys(params.monthly)[0]].id;
+ }
+
+ if (params.onetime) {
+ onetime_id = params.onetime[Object.keys(params.onetime)[0]].id;
+ // If onetime ID === 999 it's a custom plan, send datas instead.
+ onetime_id = (onetime_id + '' === '999' ? params.onetime[Object.keys(params.onetime)[0]].data : onetime_id);
+ }
+
+ if (! params.period) {
+ w.imagify.info('No period defined');
+ return;
+ }
+
+ key = imagifyModal.getApiKey();
+ rt_onetime = onetime_id;
+ rt_yearly = 'yearly' === params.period ? monthly_id : 0;
+ rt_monthly = 'monthly' === params.period ? monthly_id : 0;
+ coupon = $('#imagify-coupon-code').val();
+ rt_coupon = '' === coupon ? 'none' : coupon;
+ // Not used but...
+ amount = parseFloat($('.imagify-global-amount').text()).toFixed(2);
+
+ // Compose route.
+ // pay_src + :ontimeplan(0)/:monthlyplan(0)/:yearlyplan(0)/:coupon(none)/
+ pay_src = pay_src + rt_onetime + '/' + rt_monthly + '/' + rt_yearly + '/' + rt_coupon + '/';
+
+ // iFrame sort of cache fix.
+ $iframeClone = $iframe.remove().attr('src', pay_src);
+
+ imagifyModal.$paymentView.html($iframeClone);
+ },
+
+ /**
+ * Public function triggered by payement iframe.
+ */
+ paymentClose: function () {
+ $('.imagify-iframe-viewing .close-btn').trigger('click.imagify');
+ $('.imagify-iframe-viewing').removeClass('imagify-iframe-viewing');
+ },
+
+ /**
+ * @uses imagifyModal.switchToView()
+ */
+ paymentBack: function () {
+ imagifyModal.switchToView(imagifyModal.$preView);
+ },
+
+ /**
+ * @uses imagifyModal.switchToView()
+ */
+ paymentSuccess: function () {
+ imagifyModal.switchToView(imagifyModal.$successView);
+ },
+
+ /**
+ * @uses imagifyModal.paymentClose()
+ * @uses imagifyModal.paymentBack()
+ * @uses imagifyModal.paymentSuccess()
+ */
+ checkPluginMessage: function (e) {
+ var origin = e.origin || e.originalEvent.origin; // eslint-disable-line no-shadow
+
+ if ('https://app.imagify.io' !== origin && 'http://dapp.imagify.io' !== origin) {
+ return;
+ }
+
+ switch (e.data) {
+ case 'cancel':
+ imagifyModal.paymentClose();
+ break;
+ case 'back':
+ imagifyModal.paymentBack();
+ break;
+ case 'success':
+ imagifyModal.paymentSuccess();
+ break;
+ }
+ }
+ };
+
+ /**
+ * INIT.
+ */
+
+ // Check all boxes on load.
+ imagifyModal.checkCheckbox(imagifyModal.$checkboxes);
+ imagifyModal.checkRadio(imagifyModal.$radios.filter(':checked'));
+
+ // Check coupon onload.
+ imagifyModal.checkCoupon();
+
+ // Check the changed box.
+ imagifyModal.$checkboxes.on('change.imagify', function () {
+ imagifyModal.checkCheckbox($(this));
+ });
+
+ // Check the radio box.
+ imagifyModal.$radios.on('change.imagify', function () {
+ imagifyModal.checkRadio($(this));
+ });
+
+ /**
+ * Get pricings on modal opening.
+ * Build the pricing tables inside modal.
+ */
+ $('#imagify-get-pricing-modal').on('click.imagify-ajax', function () {
+ imagifyModal.getPricing($(this));
+ });
+
+ /**
+ * Reset the modal on close.
+ */
+ $(d).on('modalClosed.imagify', '.imagify-payment-modal', function () {
+ // Reset viewing class & aria-labelledby.
+ $(this).find('.imagify-modal-content').removeClass('imagify-success-viewing imagify-iframe-viewing');
+
+ // Reset first view after fadeout ~= 300 ms.
+ setTimeout(function () {
+ $('.imagify-modal-views').hide();
+ $('#imagify-pre-checkout-view').show();
+ }, 300);
+ });
+
+ /**
+ * Get validation for Coupon Code
+ * - On blur
+ * - On Enter or Spacebar press
+ * - On click OK button
+ *
+ * @since 1.6.3 Only if field hasn't readonly attribute (discount auto-applied).
+ */
+ $('#imagify-coupon-code').on('blur.imagify', function () {
+ if (! $(this).attr('readonly')) {
+ imagifyModal.checkCoupon();
+ }
+ }).on('keydown.imagify', function (e) {
+ var $this = $(this);
+
+ if ($this.attr('readonly')) {
+ return;
+ }
+ if (13 === e.keyCode || 32 === e.keyCode) {
+ imagifyModal.checkCoupon();
+ return false;
+ }
+ if ($this.val().length >= 3) {
+ $this.closest('.imagify-coupon-input').addClass('imagify-canbe-validate');
+ } else {
+ $this.closest('.imagify-coupon-input').removeClass('imagify-canbe-validate');
+ }
+ });
+
+ $('#imagify-coupon-validate').on('click.imagify', function () {
+ imagifyModal.checkCoupon();
+ $(this).closest('.imagify-canbe-validate').removeClass('imagify-canbe-validate');
+ });
+
+ /**
+ * View game, step by step.
+ */
+
+ // 1) when you decide to choose another plan.
+
+ /**
+ * 1.a) on click, display choices.
+ *
+ * @uses imagifyModal.switchToView()
+ */
+ imagifyModal.$anotherBtn.on('click.imagify', function (e) {
+ var type = $(this).data('imagify-choose'),
+ tab = 'imagify-pricing-tab-' + ('plan' === type ? 'monthly' : 'onetime');
+
+ e.preventDefault();
+
+ imagifyModal.switchToView(imagifyModal.$plansView, { tab: tab });
+ });
+
+ /**
+ * 1.b) on click in a choice, return to pre-checkout step.
+ *
+ * @uses imagifyModal.getHtmlPrice()
+ * @uses imagifyModal.switchToView()
+ * @uses imagifyModal.populatePayBtn()
+ */
+ imagifyModal.$modal.on('click.imagify', '.imagify-payment-btn-select-plan', function (e) {
+ var $_this = $(this),
+ $offer_line = $_this.closest('.imagify-offer-line'),
+ datas = $_this.data('offer'),
+ datas_str = $_this.attr('data-offer'),
+ is_onetime = $_this.closest('.imagify-tab-content').attr('id') !== 'imagify-pricing-tab-monthly',
+ $target_line = is_onetime ? imagifyModal.$preView.find('.imagify-offer-onetime') : imagifyModal.$preView.find('.imagify-offer-monthly'),
+ period = is_onetime ? null : ($_this.closest('.imagify-pricing-table').hasClass('imagify-month-selected') ? 'monthly' : 'yearly'),
+ price = is_onetime ? imagifyModal.getHtmlPrice(datas[Object.keys(datas)[0]].price) : imagifyModal.getHtmlPrice(datas[Object.keys(datas)[0]].prices, period),
+ monthly_txt = is_onetime ? '' : '' + $offer_line.find('.imagify-price-by').text() + ' ',
+ discount = $offer_line.find('.imagify-price-discount').html(),
+ imgs = $offer_line.find('.imagify-approx-nb').text(),
+ offer_size = $offer_line.find('.imagify-offer-size').text();
+
+ e.preventDefault();
+
+ // Change views to go back pre-checkout.
+ imagifyModal.switchToView(imagifyModal.$preView);
+
+ // Change price (+ "/month" if found in monthly plans).
+ $target_line.find('.imagify-number-block').html(price + monthly_txt);
+
+ // Change discount.
+ $target_line.find('.imagify-price-discount').html(discount);
+
+ // Change approx images nb.
+ $target_line.find('.imagify-approx-nb').text(imgs);
+
+ // Change offer size name.
+ $target_line.find('.imagify-offer-size').text(offer_size);
+
+ // Change datas (json).
+ $target_line.attr('data-offer', datas_str);
+
+ if (! is_onetime) {
+ $target_line.find('.imagify-price-add-data').text($offer_line.find('.imagify-price-add-data').text());
+
+ // Trigger period selected from offer selection view to pre-checkout view.
+ if ('monthly' === period) {
+ $target_line.find('#imagify-subscription-monthly').trigger('click.imagify');
+ } else {
+ $target_line.find('#imagify-subscription-yearly').trigger('click.imagify');
+ }
+ $target_line.find('.imagify-inline-options').find('input:radio:checked').trigger('change.imagify');
+ }
+
+ // Update price information in button.
+ imagifyModal.populatePayBtn();
+ });
+
+ /**
+ * 2) when you checkout.
+ *
+ * @uses imagifyModal.switchToView()
+ * @uses imagifyModal.getPeriod()
+ * @uses imagifyModal.iframeSetSrc()
+ */
+ $('#imagify-modal-checkout-btn').on('click.imagify', function (e) {
+ var $monthly_offer, $onetime_offer, checkout_datas;
+
+ e.preventDefault();
+
+ // Do nothing if button disabled.
+ if ($(this).hasClass('imagify-button-disabled')) {
+ return;
+ }
+
+ $monthly_offer = $('.imagify-offer-monthly');
+ $onetime_offer = $('.imagify-offer-onetime');
+ checkout_datas = {};
+
+ // If user choose a monthly plan.
+ if ($monthly_offer.hasClass('imagify-offer-selected')) {
+ checkout_datas.monthly = JSON.parse($monthly_offer.attr('data-offer'));
+ }
+
+ // If user choose a One Time plan.
+ if ($onetime_offer.hasClass('imagify-offer-selected')) {
+ checkout_datas.onetime = JSON.parse($onetime_offer.attr('data-offer'));
+ }
+
+ // Clear user account cache.
+ if (imagifyPricingModal.userDataCache) {
+ $.post(ajaxurl, {
+ action: imagifyPricingModal.userDataCache.deleteAction,
+ _wpnonce: imagifyPricingModal.userDataCache.deleteNonce
+ });
+ }
+
+ // Change views to go to checkout/payment view.
+ imagifyModal.switchToView(imagifyModal.$paymentView);
+
+ checkout_datas.period = imagifyModal.getPeriod();
+
+ imagifyModal.iframeSetSrc(checkout_datas);
+ });
+
+ /**
+ * Go back to previous step ("Choose Another Plan" links).
+ */
+ $('.imagify-back-to-plans').on('click.imagify', function (e) {
+ var $_this = $(this),
+ is_onetime = $_this.closest('.imagify-cart-item').hasClass('imagify-cart-item-onetime');
+
+ e.preventDefault();
+
+ if (is_onetime) {
+ $('.imagify-offer-onetime').find('.imagify-choose-another-plan').trigger('click.imagify');
+ } else {
+ $('.imagify-offer-monthly').find('.imagify-choose-another-plan').trigger('click.imagify');
+ }
+ });
+
+ // Message/communication API.
+ w.addEventListener('message', imagifyModal.checkPluginMessage, true);
+
+})(jQuery, document, window);
diff --git a/wp-content/plugins/imagify/assets/js/pricing-modal.min.js b/wp-content/plugins/imagify/assets/js/pricing-modal.min.js
new file mode 100644
index 00000000..6b8965ee
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/pricing-modal.min.js
@@ -0,0 +1 @@
+!function(a,b,c,d){a(b).on("click.imagify",".imagify-tab",function(b){var c,d=a(this);b.preventDefault(),d.hasClass("imagify-current")||(c=d.find("a").attr("href")||"#"+d.find("a").attr("aria-controls"),d.closest(".imagify-tabs").next(".imagify-tabs-contents").find(".imagify-tab-content").hide().attr("aria-hidden","true"),a(c).fadeIn(275).attr("aria-hidden","false"),d.closest(".imagify-tabs").find(".imagify-tab").removeClass("imagify-current").attr("aria-selected","false"),d.addClass("imagify-current").attr("aria-selected","true"))})}(jQuery,document,window),function(a,b,c,d){var e={};a("#imagify-pricing-modal").length&&(e={$modal:a("#imagify-pricing-modal"),$checkboxes:a(".imagify-offer-line .imagify-checkbox"),$radios:a(".imagify-payment-modal .imagify-radio-line input"),$preView:a("#imagify-pre-checkout-view"),$plansView:a("#imagify-plans-selection-view").hide(),$paymentView:a("#imagify-payment-process-view").hide(),$successView:a("#imagify-success-view").hide(),$anotherBtn:a(".imagify-choose-another-plan"),speedFadeIn:300,getHtmlPrice:function(a,b){var c,d,e,f,g;return b||(b=null),"object"!=typeof a?(a+="",a=a.split("."),a[1]=1===a[1].length?a[1]+"0":(""+a[1]).substring(0,2),g=''+a[0]+" ",g+='.'+a[1]+" "):(c=a.monthly+"",d=a.yearly+"",e="0"===c?["0","00"]:c.split("."),f="0"===d?["0","00"]:d.split("."),g='',g+='',g+=''+e[0]+" ",g+='.'+(1===e[1].length?e[1]+"0":(""+e[1]).substring(0,2))+" ",g+=" ",g+='',g+=''+f[0]+" ",g+='.'+(1===f[1].length?f[1]+"0":(""+f[1]).substring(0,2))+" ",g+=" ",g+=" ")},getHtmlDiscountPrice:function(a,b){var c,d,e="";return b||(b=null),"object"==typeof a?(c=a.monthly+"",d=a.yearly+"",e+='',e+='$ ',e+='',e+='',e+=''+c+" ",e+=" ",e+='',e+=''+d+" ",e+=" ",e+=" ",e+=" "):(a+="",e+='',e+='$ ',e+=''+a+" ",e+=" "),e},populateOffer:function(a,b,d,f){var g,h,i,j=c.imagify_discount_datas,k=b.additional_gb,l=b.annual_cost,m=b.id,n=b.label,o=b.monthly_cost,p=b.quota,q=b.cost,r=-1===p?"Unlimited":p>=1e3?p/1e3+" GB":p+" MB",s="monthly"===d?{monthly:o,yearly:Math.round(l/12*100)/100}:q,t=s;return j.is_active&&"percentage"===j.coupon_type&&"monthly"===d&&(g=(100-j.coupon_value)/100,s="monthly"===d?{monthly:o*g,yearly:Math.round(l*g/12*100)/100}:q*g),void 0!==f&&a.addClass("imagify-"+d+"-"+n+f),a.find(".imagify-offer-size").text(r),a.find(".imagify-number-block").html(e.getHtmlPrice(s,"monthly")),j.is_active&&"percentage"===j.coupon_type&&"monthly"===d&&(a.find(".imagify-price-block").prev(".imagify-price-discount").remove(),a.find(".imagify-price-block").before(e.getHtmlDiscountPrice(t,"monthly"))),a.find(".imagify-approx-nb").text(5*p),"monthly"===d&&a.find(".imagify-price-add-data").text("$"+k),h=a.find(".imagify-payment-btn-select-plan").length?a.find(".imagify-payment-btn-select-plan"):a,i="monthly"===d?'{"'+n+'":{"id":'+m+',"name":"'+r+'","data":'+p+',"dataf":"'+r+'","imgs":'+5*p+',"prices":{"monthly":'+s.monthly+',"yearly":'+s.yearly+',"add":'+k+"}}}":'{"ot'+n+'":{"id":'+m+',"name":"'+r+'","data":'+p+',"dataf":"'+r+'","imgs":'+5*p+',"price":'+s+"}}",h.attr("data-offer",i),a},populatePayBtn:function(){var b,c,d=a(".imagify-offer-monthly"),e=a(".imagify-offer-onetime"),f=0,g=0,h=0;d.length&&(b=JSON.parse(d.attr("data-offer")),d.hasClass("imagify-offer-selected")&&(g=a("#imagify-subscription-monthly").filter(":checked").length?b[Object.keys(b)[0]].prices.monthly:12*b[Object.keys(b)[0]].prices.yearly)),e.length&&(c=JSON.parse(e.attr("data-offer")),e.hasClass("imagify-offer-selected")&&(h=c[Object.keys(c)[0]].price)),f=parseFloat(h+g).toFixed(2),"0.00"===f||0===f?a("#imagify-modal-checkout-btn").attr("disabled","disabled").addClass("imagify-button-disabled"):a("#imagify-modal-checkout-btn").removeAttr("disabled").removeClass("imagify-button-disabled")},checkCoupon:function(){var b,c,d,e,f=a("#imagify-coupon-code").val();""!==f&&(b=a(".imagify-coupon-text"),c=b.find("label"),d=a(".imagify-coupon-section"),e=a("#imagify-get-pricing-modal").data("nonce"),b.addClass("checking"),a.post(ajaxurl,{action:"imagify_check_coupon",coupon:f,imagifynonce:e},function(a){var e;b.removeClass("checking"),a.success?a.data.success?(e="percentage"===a.data.coupon_type?a.data.value+"%":"$"+a.data.value,d.removeClass("invalid").addClass("validated"),c.html(imagifyPricingModal.labels.successCouponAPI),c.find(".imagify-coupon-offer").text(e),c.find(".imagify-coupon-word").text(f)):(d.removeClass("validated").addClass("invalid"),c.text(a.data.detail)):(d.removeClass("validated").addClass("invalid"),c.text(imagifyPricingModal.labels.errorCouponAPI))}))},getPricing:function(b){var d=b.data("nonce"),f={action:"imagify_get_prices",imagifynonce:d},g={action:"imagify_get_images_counts",imagifynonce:d},h={action:"imagify_get_discount",imagifynonce:d};e.$modal.find(".imagify-modal-loader").hide().show(),a.post(ajaxurl,f,function(b){if(!b.success)return void e.populatePayBtn();a.post(ajaxurl,g,function(d){d.success&&a.post(ajaxurl,h,function(f){var g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w=25,x="",y="";if(f.success){if(g=d.data,h=b.data,i=f.data,j={mo:[],ot:[]},k={month:g.average_month_size.raw/Math.pow(1024,2),total:g.total_library_size.raw/Math.pow(1024,2)},m=a("#imagify-offer-monthly-template"),n=a("#imagify-offer-onetime-template"),o=n.html(),p=m.html(),q=a(".imagify-estimation-block"),a.each(h.monthlies,function(a,b){(void 0===b.active||void 0!==b.active&&!0===b.active)&&("free"===b.label&&(w=b.quota),j.mo.push(b))}),a.each(h.onetimes,function(a,b){(void 0===b.active||void 0!==b.active&&!0===b.active)&&j.ot.push(b)}),q.removeClass("imagify-analyzing"),q.find(".average-month-size").text(g.average_month_size.human),q.find(".total-library-size").text(g.total_library_size.human),k.total+k.month'+imagifyPricingModal.labels.errorPriceAPI+"
"),void e.$modal.find(".imagify-modal-loader").fadeOut(300);c.imagify_discount_datas=i,i.is_active&&(s=a(".imagify-modal-promotion"),t=i.date_end.split("T")[0],u=i.coupon_value,v="percentage"===i.coupon_type?u+"%":"$"+u,a("#imagify-coupon-code").val(i.label).attr("readonly",!0),s.addClass("active").attr("aria-hidden","false"),s.find(".imagify-promotion-number").text(v),s.find(".imagify-promotion-date").text(t),e.checkCoupon()),l=e.getSuggestedOffers(j,k,w),0===j.mo.length?(a(".imagify-pre-checkout-offers .imagify-offer-monthly").remove(),a(".imagify-tabs").remove(),a(".imagify-pricing-tab-monthly").remove()):a.each(j.mo,function(b,c){var d,f,g="";if(b-l.mo.index>2)return!0;b===l.mo.index&&(f=a(".imagify-pre-checkout-offers .imagify-offer-monthly"),l.mo.selected&&(g=" imagify-offer-selected",f.addClass("imagify-offer-selected").find(".imagify-checkbox").prop("checked",!0)),e.populateOffer(f,c,"monthly")),d=a(p).clone(),d=e.populateOffer(d,c,"monthly",g),y+=d[0].outerHTML}),0===j.ot.length?(a(".imagify-pre-checkout-offers .imagify-offer-onetime").remove(),a(".imagify-tabs").remove(),a(".imagify-pricing-tab-onetime").remove()):a.each(j.ot,function(b,c){var d,f,g="";b===l.ot.index&&(f=a(".imagify-pre-checkout-offers .imagify-offer-onetime"),l.ot.selected&&(g=" imagify-offer-selected",f.addClass("imagify-offer-selected").find(".imagify-checkbox").prop("checked",!0)),e.populateOffer(f,c,"onetime")),d=a(o).clone(),d=e.populateOffer(d,c,"onetime",g),x+=d[0].outerHTML}),m.parent().find(".imagify-offer-line")&&m.parent().find(".imagify-offer-line").remove(),m.before(y),n.parent().find(".imagify-offer-line")&&n.parent().find(".imagify-offer-line").remove(),n.before(x),e.$modal.find(".imagify-modal-loader").fadeOut(300)}})}),e.populatePayBtn()})},getSuggestedOffers:function(b,c,d){var e,f,g=c.total+c.month,h={quota:0},i={mo:!1,ot:!1};return a.each(b.mo,function(a,b){return 0>b.quota&&(f={index:a,selected:1}),b.quota>h.quota&&(h={index:a,selected:1,quota:b.quota}),0===b.monthly_cost&&0===b.annual_cost||(0>=b.quota&&c.month>b.quota||c.total>b.quota||(!(0<=b.quota&&c.monthb.quota)&&(e=b,i.mo={index:a,selected:d>c.month&&d>c.total?0:1}),!0)))}),!1===i.mo&&(i.mo=void 0!==f?f:h),g-=b.mo[i.mo.index].quota,0===b.ot.length?i:g<=0?(a.each(b.ot,function(a,b){return b.quota=3?c.closest(".imagify-coupon-input").addClass("imagify-canbe-validate"):c.closest(".imagify-coupon-input").removeClass("imagify-canbe-validate"))}),a("#imagify-coupon-validate").on("click.imagify",function(){e.checkCoupon(),a(this).closest(".imagify-canbe-validate").removeClass("imagify-canbe-validate")}),e.$anotherBtn.on("click.imagify",function(b){var c=a(this).data("imagify-choose"),d="imagify-pricing-tab-"+("plan"===c?"monthly":"onetime");b.preventDefault(),e.switchToView(e.$plansView,{tab:d})}),e.$modal.on("click.imagify",".imagify-payment-btn-select-plan",function(b){var c=a(this),d=c.closest(".imagify-offer-line"),f=c.data("offer"),g=c.attr("data-offer"),h="imagify-pricing-tab-monthly"!==c.closest(".imagify-tab-content").attr("id"),i=h?e.$preView.find(".imagify-offer-onetime"):e.$preView.find(".imagify-offer-monthly"),j=h?null:c.closest(".imagify-pricing-table").hasClass("imagify-month-selected")?"monthly":"yearly",k=h?e.getHtmlPrice(f[Object.keys(f)[0]].price):e.getHtmlPrice(f[Object.keys(f)[0]].prices,j),l=h?"":''+d.find(".imagify-price-by").text()+" ",m=d.find(".imagify-price-discount").html(),n=d.find(".imagify-approx-nb").text(),o=d.find(".imagify-offer-size").text();b.preventDefault(),e.switchToView(e.$preView),i.find(".imagify-number-block").html(k+l),i.find(".imagify-price-discount").html(m),i.find(".imagify-approx-nb").text(n),i.find(".imagify-offer-size").text(o),i.attr("data-offer",g),h||(i.find(".imagify-price-add-data").text(d.find(".imagify-price-add-data").text()),"monthly"===j?i.find("#imagify-subscription-monthly").trigger("click.imagify"):i.find("#imagify-subscription-yearly").trigger("click.imagify"),i.find(".imagify-inline-options").find("input:radio:checked").trigger("change.imagify")),e.populatePayBtn()}),a("#imagify-modal-checkout-btn").on("click.imagify",function(b){var c,d,f;b.preventDefault(),a(this).hasClass("imagify-button-disabled")||(c=a(".imagify-offer-monthly"),d=a(".imagify-offer-onetime"),f={},c.hasClass("imagify-offer-selected")&&(f.monthly=JSON.parse(c.attr("data-offer"))),d.hasClass("imagify-offer-selected")&&(f.onetime=JSON.parse(d.attr("data-offer"))),imagifyPricingModal.userDataCache&&a.post(ajaxurl,{action:imagifyPricingModal.userDataCache.deleteAction,_wpnonce:imagifyPricingModal.userDataCache.deleteNonce}),e.switchToView(e.$paymentView),f.period=e.getPeriod(),e.iframeSetSrc(f))}),a(".imagify-back-to-plans").on("click.imagify",function(b){var c=a(this),d=c.closest(".imagify-cart-item").hasClass("imagify-cart-item-onetime");b.preventDefault(),d?a(".imagify-offer-onetime").find(".imagify-choose-another-plan").trigger("click.imagify"):a(".imagify-offer-monthly").find(".imagify-choose-another-plan").trigger("click.imagify")}),c.addEventListener("message",e.checkPluginMessage,!0))}(jQuery,document,window);
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/assets/js/sweetalert2.js b/wp-content/plugins/imagify/assets/js/sweetalert2.js
new file mode 100644
index 00000000..aa72e1f3
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/sweetalert2.js
@@ -0,0 +1,1641 @@
+/*!
+ * sweetalert2 v6.6.6
+ * Released under the MIT License.
+ */
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.Sweetalert2 = factory());
+}(this, (function () { 'use strict';
+
+var defaultParams = {
+ title: '',
+ titleText: '',
+ text: '',
+ html: '',
+ type: null,
+ customClass: '',
+ target: 'body',
+ animation: true,
+ allowOutsideClick: true,
+ allowEscapeKey: true,
+ allowEnterKey: true,
+ showConfirmButton: true,
+ showCancelButton: false,
+ preConfirm: null,
+ confirmButtonText: 'OK',
+ confirmButtonColor: '#3085d6',
+ confirmButtonClass: null,
+ cancelButtonText: 'Cancel',
+ cancelButtonColor: '#aaa',
+ cancelButtonClass: null,
+ buttonsStyling: true,
+ reverseButtons: false,
+ focusCancel: false,
+ showCloseButton: false,
+ showLoaderOnConfirm: false,
+ imageUrl: null,
+ imageWidth: null,
+ imageHeight: null,
+ imageClass: null,
+ timer: null,
+ width: 500,
+ padding: 20,
+ background: '#fff',
+ input: null,
+ inputPlaceholder: '',
+ inputValue: '',
+ inputOptions: {},
+ inputAutoTrim: true,
+ inputClass: null,
+ inputAttributes: {},
+ inputValidator: null,
+ progressSteps: [],
+ currentProgressStep: null,
+ progressStepsDistance: '40px',
+ onOpen: null,
+ onClose: null,
+ useRejections: true
+};
+
+var swalPrefix = 'swal2-';
+
+var prefix = function prefix(items) {
+ var result = {};
+ for (var i in items) {
+ result[items[i]] = swalPrefix + items[i];
+ }
+ return result;
+};
+
+var swalClasses = prefix(['container', 'shown', 'iosfix', 'modal', 'overlay', 'fade', 'show', 'hide', 'noanimation', 'close', 'title', 'content', 'buttonswrapper', 'confirm', 'cancel', 'icon', 'image', 'input', 'file', 'range', 'select', 'radio', 'checkbox', 'textarea', 'inputerror', 'validationerror', 'progresssteps', 'activeprogressstep', 'progresscircle', 'progressline', 'loading', 'styled']);
+
+var iconTypes = prefix(['success', 'warning', 'info', 'question', 'error']);
+
+/*
+ * Set hover, active and focus-states for buttons (source: http://www.sitepoint.com/javascript-generate-lighter-darker-color)
+ */
+var colorLuminance = function colorLuminance(hex, lum) {
+ // Validate hex string
+ hex = String(hex).replace(/[^0-9a-f]/gi, '');
+ if (hex.length < 6) {
+ hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
+ }
+ lum = lum || 0;
+
+ // Convert to decimal and change luminosity
+ var rgb = '#';
+ for (var i = 0; i < 3; i++) {
+ var c = parseInt(hex.substr(i * 2, 2), 16);
+ c = Math.round(Math.min(Math.max(0, c + c * lum), 255)).toString(16);
+ rgb += ('00' + c).substr(c.length);
+ }
+
+ return rgb;
+};
+
+var uniqueArray = function uniqueArray(arr) {
+ var result = [];
+ for (var i in arr) {
+ if (result.indexOf(arr[i]) === -1) {
+ result.push(arr[i]);
+ }
+ }
+ return result;
+};
+
+/* global MouseEvent */
+
+// Remember state in cases where opening and handling a modal will fiddle with it.
+var states = {
+ previousWindowKeyDown: null,
+ previousActiveElement: null,
+ previousBodyPadding: null
+
+ /*
+ * Add modal + overlay to DOM
+ */
+};var init = function init(params) {
+ if (typeof document === 'undefined') {
+ console.error('SweetAlert2 requires document to initialize');
+ return;
+ }
+
+ var container = document.createElement('div');
+ container.className = swalClasses.container;
+ container.innerHTML = sweetHTML;
+
+ var targetElement = document.querySelector(params.target);
+ if (!targetElement) {
+ console.warn('SweetAlert2: Can\'t find the target "' + params.target + '"');
+ targetElement = document.body;
+ }
+ targetElement.appendChild(container);
+
+ var modal = getModal();
+ var input = getChildByClass(modal, swalClasses.input);
+ var file = getChildByClass(modal, swalClasses.file);
+ var range = modal.querySelector('.' + swalClasses.range + ' input');
+ var rangeOutput = modal.querySelector('.' + swalClasses.range + ' output');
+ var select = getChildByClass(modal, swalClasses.select);
+ var checkbox = modal.querySelector('.' + swalClasses.checkbox + ' input');
+ var textarea = getChildByClass(modal, swalClasses.textarea);
+
+ input.oninput = function () {
+ sweetAlert.resetValidationError();
+ };
+
+ input.onkeydown = function (event) {
+ setTimeout(function () {
+ if (event.keyCode === 13 && params.allowEnterKey) {
+ event.stopPropagation();
+ sweetAlert.clickConfirm();
+ }
+ }, 0);
+ };
+
+ file.onchange = function () {
+ sweetAlert.resetValidationError();
+ };
+
+ range.oninput = function () {
+ sweetAlert.resetValidationError();
+ rangeOutput.value = range.value;
+ };
+
+ range.onchange = function () {
+ sweetAlert.resetValidationError();
+ range.previousSibling.value = range.value;
+ };
+
+ select.onchange = function () {
+ sweetAlert.resetValidationError();
+ };
+
+ checkbox.onchange = function () {
+ sweetAlert.resetValidationError();
+ };
+
+ textarea.oninput = function () {
+ sweetAlert.resetValidationError();
+ };
+
+ return modal;
+};
+
+/*
+ * Manipulate DOM
+ */
+
+var sweetHTML = ('\n \n
\n
\n \n
\n
?
\n
!
\n
i
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
\n
\n
\n \n \n
\n
\n
\n OK \n Cancel \n
\n
\xD7 \n
\n').replace(/(^|\n)\s*/g, '');
+
+var getContainer = function getContainer() {
+ return document.body.querySelector('.' + swalClasses.container);
+};
+
+var getModal = function getModal() {
+ return getContainer() ? getContainer().querySelector('.' + swalClasses.modal) : null;
+};
+
+var getIcons = function getIcons() {
+ var modal = getModal();
+ return modal.querySelectorAll('.' + swalClasses.icon);
+};
+
+var elementByClass = function elementByClass(className) {
+ return getContainer() ? getContainer().querySelector('.' + className) : null;
+};
+
+var getTitle = function getTitle() {
+ return elementByClass(swalClasses.title);
+};
+
+var getContent = function getContent() {
+ return elementByClass(swalClasses.content);
+};
+
+var getImage = function getImage() {
+ return elementByClass(swalClasses.image);
+};
+
+var getButtonsWrapper = function getButtonsWrapper() {
+ return elementByClass(swalClasses.buttonswrapper);
+};
+
+var getProgressSteps = function getProgressSteps() {
+ return elementByClass(swalClasses.progresssteps);
+};
+
+var getValidationError = function getValidationError() {
+ return elementByClass(swalClasses.validationerror);
+};
+
+var getConfirmButton = function getConfirmButton() {
+ return elementByClass(swalClasses.confirm);
+};
+
+var getCancelButton = function getCancelButton() {
+ return elementByClass(swalClasses.cancel);
+};
+
+var getCloseButton = function getCloseButton() {
+ return elementByClass(swalClasses.close);
+};
+
+var getFocusableElements = function getFocusableElements(focusCancel) {
+ var buttons = [getConfirmButton(), getCancelButton()];
+ if (focusCancel) {
+ buttons.reverse();
+ }
+ var focusableElements = buttons.concat(Array.prototype.slice.call(getModal().querySelectorAll('button, input:not([type=hidden]), textarea, select, a, *[tabindex]:not([tabindex="-1"])')));
+ return uniqueArray(focusableElements);
+};
+
+var hasClass = function hasClass(elem, className) {
+ if (elem.classList) {
+ return elem.classList.contains(className);
+ }
+ return false;
+};
+
+var focusInput = function focusInput(input) {
+ input.focus();
+
+ // place cursor at end of text in text input
+ if (input.type !== 'file') {
+ // http://stackoverflow.com/a/2345915/1331425
+ var val = input.value;
+ input.value = '';
+ input.value = val;
+ }
+};
+
+var addClass = function addClass(elem, className) {
+ if (!elem || !className) {
+ return;
+ }
+ var classes = className.split(/\s+/).filter(Boolean);
+ classes.forEach(function (className) {
+ elem.classList.add(className);
+ });
+};
+
+var removeClass = function removeClass(elem, className) {
+ if (!elem || !className) {
+ return;
+ }
+ var classes = className.split(/\s+/).filter(Boolean);
+ classes.forEach(function (className) {
+ elem.classList.remove(className);
+ });
+};
+
+var getChildByClass = function getChildByClass(elem, className) {
+ for (var i = 0; i < elem.childNodes.length; i++) {
+ if (hasClass(elem.childNodes[i], className)) {
+ return elem.childNodes[i];
+ }
+ }
+};
+
+var show = function show(elem, display) {
+ if (!display) {
+ display = 'block';
+ }
+ elem.style.opacity = '';
+ elem.style.display = display;
+};
+
+var hide = function hide(elem) {
+ elem.style.opacity = '';
+ elem.style.display = 'none';
+};
+
+var empty = function empty(elem) {
+ while (elem.firstChild) {
+ elem.removeChild(elem.firstChild);
+ }
+};
+
+// borrowed from jqeury $(elem).is(':visible') implementation
+var isVisible = function isVisible(elem) {
+ return elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length;
+};
+
+var removeStyleProperty = function removeStyleProperty(elem, property) {
+ if (elem.style.removeProperty) {
+ elem.style.removeProperty(property);
+ } else {
+ elem.style.removeAttribute(property);
+ }
+};
+
+var fireClick = function fireClick(node) {
+ if (!isVisible(node)) {
+ return false;
+ }
+
+ // Taken from http://www.nonobtrusive.com/2011/11/29/programatically-fire-crossbrowser-click-event-with-javascript/
+ // Then fixed for today's Chrome browser.
+ if (typeof MouseEvent === 'function') {
+ // Up-to-date approach
+ var mevt = new MouseEvent('click', {
+ view: window,
+ bubbles: false,
+ cancelable: true
+ });
+ node.dispatchEvent(mevt);
+ } else if (document.createEvent) {
+ // Fallback
+ var evt = document.createEvent('MouseEvents');
+ evt.initEvent('click', false, false);
+ node.dispatchEvent(evt);
+ } else if (document.createEventObject) {
+ node.fireEvent('onclick');
+ } else if (typeof node.onclick === 'function') {
+ node.onclick();
+ }
+};
+
+var animationEndEvent = function () {
+ var testEl = document.createElement('div');
+ var transEndEventNames = {
+ 'WebkitAnimation': 'webkitAnimationEnd',
+ 'OAnimation': 'oAnimationEnd oanimationend',
+ 'msAnimation': 'MSAnimationEnd',
+ 'animation': 'animationend'
+ };
+ for (var i in transEndEventNames) {
+ if (transEndEventNames.hasOwnProperty(i) && testEl.style[i] !== undefined) {
+ return transEndEventNames[i];
+ }
+ }
+
+ return false;
+}();
+
+// Reset previous window keydown handler and focued element
+var resetPrevState = function resetPrevState() {
+ window.onkeydown = states.previousWindowKeyDown;
+ if (states.previousActiveElement && states.previousActiveElement.focus) {
+ var x = window.scrollX;
+ var y = window.scrollY;
+ states.previousActiveElement.focus();
+ if (x && y) {
+ // IE has no scrollX/scrollY support
+ window.scrollTo(x, y);
+ }
+ }
+};
+
+// Measure width of scrollbar
+// https://github.com/twbs/bootstrap/blob/master/js/modal.js#L279-L286
+var measureScrollbar = function measureScrollbar() {
+ var supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints;
+ if (supportsTouch) {
+ return 0;
+ }
+ var scrollDiv = document.createElement('div');
+ scrollDiv.style.width = '50px';
+ scrollDiv.style.height = '50px';
+ scrollDiv.style.overflow = 'scroll';
+ document.body.appendChild(scrollDiv);
+ var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
+ document.body.removeChild(scrollDiv);
+ return scrollbarWidth;
+};
+
+// JavaScript Debounce Function
+// Simplivied version of https://davidwalsh.name/javascript-debounce-function
+var debounce = function debounce(func, wait) {
+ var timeout = void 0;
+ return function () {
+ var later = function later() {
+ timeout = null;
+ func();
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+};
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
+ return typeof obj;
+} : function (obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+var _extends = Object.assign || function (target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i];
+
+ for (var key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+
+ return target;
+};
+
+var modalParams = _extends({}, defaultParams);
+var queue = [];
+var swal2Observer = void 0;
+
+/*
+ * Set type, text and actions on modal
+ */
+var setParameters = function setParameters(params) {
+ var modal = getModal() || init(params);
+
+ for (var param in params) {
+ if (!defaultParams.hasOwnProperty(param) && param !== 'extraParams') {
+ console.warn('SweetAlert2: Unknown parameter "' + param + '"');
+ }
+ }
+
+ // Set modal width
+ modal.style.width = typeof params.width === 'number' ? params.width + 'px' : params.width;
+
+ modal.style.padding = params.padding + 'px';
+ modal.style.background = params.background;
+ var successIconParts = modal.querySelectorAll('[class^=swal2-success-circular-line], .swal2-success-fix');
+ for (var i = 0; i < successIconParts.length; i++) {
+ successIconParts[i].style.background = params.background;
+ }
+
+ var title = getTitle();
+ var content = getContent();
+ var buttonsWrapper = getButtonsWrapper();
+ var confirmButton = getConfirmButton();
+ var cancelButton = getCancelButton();
+ var closeButton = getCloseButton();
+
+ // Title
+ if (params.titleText) {
+ title.innerText = params.titleText;
+ } else {
+ title.innerHTML = params.title.split('\n').join(' ');
+ }
+
+ // Content
+ if (params.text || params.html) {
+ if (_typeof(params.html) === 'object') {
+ content.innerHTML = '';
+ if (0 in params.html) {
+ for (var _i = 0; _i in params.html; _i++) {
+ content.appendChild(params.html[_i].cloneNode(true));
+ }
+ } else {
+ content.appendChild(params.html.cloneNode(true));
+ }
+ } else if (params.html) {
+ content.innerHTML = params.html;
+ } else if (params.text) {
+ content.textContent = params.text;
+ }
+ show(content);
+ } else {
+ hide(content);
+ }
+
+ // Close button
+ if (params.showCloseButton) {
+ show(closeButton);
+ } else {
+ hide(closeButton);
+ }
+
+ // Custom Class
+ modal.className = swalClasses.modal;
+ if (params.customClass) {
+ addClass(modal, params.customClass);
+ }
+
+ // Progress steps
+ var progressStepsContainer = getProgressSteps();
+ var currentProgressStep = parseInt(params.currentProgressStep === null ? sweetAlert.getQueueStep() : params.currentProgressStep, 10);
+ if (params.progressSteps.length) {
+ show(progressStepsContainer);
+ empty(progressStepsContainer);
+ if (currentProgressStep >= params.progressSteps.length) {
+ console.warn('SweetAlert2: Invalid currentProgressStep parameter, it should be less than progressSteps.length ' + '(currentProgressStep like JS arrays starts from 0)');
+ }
+ params.progressSteps.forEach(function (step, index) {
+ var circle = document.createElement('li');
+ addClass(circle, swalClasses.progresscircle);
+ circle.innerHTML = step;
+ if (index === currentProgressStep) {
+ addClass(circle, swalClasses.activeprogressstep);
+ }
+ progressStepsContainer.appendChild(circle);
+ if (index !== params.progressSteps.length - 1) {
+ var line = document.createElement('li');
+ addClass(line, swalClasses.progressline);
+ line.style.width = params.progressStepsDistance;
+ progressStepsContainer.appendChild(line);
+ }
+ });
+ } else {
+ hide(progressStepsContainer);
+ }
+
+ // Icon
+ var icons = getIcons();
+ for (var _i2 = 0; _i2 < icons.length; _i2++) {
+ hide(icons[_i2]);
+ }
+ if (params.type) {
+ var validType = false;
+ for (var iconType in iconTypes) {
+ if (params.type === iconType) {
+ validType = true;
+ break;
+ }
+ }
+ if (!validType) {
+ console.error('SweetAlert2: Unknown alert type: ' + params.type);
+ return false;
+ }
+ var icon = modal.querySelector('.' + swalClasses.icon + '.' + iconTypes[params.type]);
+ show(icon);
+
+ // Animate icon
+ if (params.animation) {
+ switch (params.type) {
+ case 'success':
+ addClass(icon, 'swal2-animate-success-icon');
+ addClass(icon.querySelector('.swal2-success-line-tip'), 'swal2-animate-success-line-tip');
+ addClass(icon.querySelector('.swal2-success-line-long'), 'swal2-animate-success-line-long');
+ break;
+ case 'error':
+ addClass(icon, 'swal2-animate-error-icon');
+ addClass(icon.querySelector('.swal2-x-mark'), 'swal2-animate-x-mark');
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // Custom image
+ var image = getImage();
+ if (params.imageUrl) {
+ image.setAttribute('src', params.imageUrl);
+ show(image);
+
+ if (params.imageWidth) {
+ image.setAttribute('width', params.imageWidth);
+ } else {
+ image.removeAttribute('width');
+ }
+
+ if (params.imageHeight) {
+ image.setAttribute('height', params.imageHeight);
+ } else {
+ image.removeAttribute('height');
+ }
+
+ image.className = swalClasses.image;
+ if (params.imageClass) {
+ addClass(image, params.imageClass);
+ }
+ } else {
+ hide(image);
+ }
+
+ // Cancel button
+ if (params.showCancelButton) {
+ cancelButton.style.display = 'inline-block';
+ } else {
+ hide(cancelButton);
+ }
+
+ // Confirm button
+ if (params.showConfirmButton) {
+ removeStyleProperty(confirmButton, 'display');
+ } else {
+ hide(confirmButton);
+ }
+
+ // Buttons wrapper
+ if (!params.showConfirmButton && !params.showCancelButton) {
+ hide(buttonsWrapper);
+ } else {
+ show(buttonsWrapper);
+ }
+
+ // Edit text on cancel and confirm buttons
+ confirmButton.innerHTML = params.confirmButtonText;
+ cancelButton.innerHTML = params.cancelButtonText;
+
+ // Set buttons to selected background colors
+ if (params.buttonsStyling) {
+ confirmButton.style.backgroundColor = params.confirmButtonColor;
+ cancelButton.style.backgroundColor = params.cancelButtonColor;
+ }
+
+ // Add buttons custom classes
+ confirmButton.className = swalClasses.confirm;
+ addClass(confirmButton, params.confirmButtonClass);
+ cancelButton.className = swalClasses.cancel;
+ addClass(cancelButton, params.cancelButtonClass);
+
+ // Buttons styling
+ if (params.buttonsStyling) {
+ addClass(confirmButton, swalClasses.styled);
+ addClass(cancelButton, swalClasses.styled);
+ } else {
+ removeClass(confirmButton, swalClasses.styled);
+ removeClass(cancelButton, swalClasses.styled);
+
+ confirmButton.style.backgroundColor = confirmButton.style.borderLeftColor = confirmButton.style.borderRightColor = '';
+ cancelButton.style.backgroundColor = cancelButton.style.borderLeftColor = cancelButton.style.borderRightColor = '';
+ }
+
+ // CSS animation
+ if (params.animation === true) {
+ removeClass(modal, swalClasses.noanimation);
+ } else {
+ addClass(modal, swalClasses.noanimation);
+ }
+};
+
+/*
+ * Animations
+ */
+var openModal = function openModal(animation, onComplete) {
+ var container = getContainer();
+ var modal = getModal();
+
+ if (animation) {
+ addClass(modal, swalClasses.show);
+ addClass(container, swalClasses.fade);
+ removeClass(modal, swalClasses.hide);
+ } else {
+ removeClass(modal, swalClasses.fade);
+ }
+ show(modal);
+
+ // scrolling is 'hidden' until animation is done, after that 'auto'
+ container.style.overflowY = 'hidden';
+ if (animationEndEvent && !hasClass(modal, swalClasses.noanimation)) {
+ modal.addEventListener(animationEndEvent, function swalCloseEventFinished() {
+ modal.removeEventListener(animationEndEvent, swalCloseEventFinished);
+ container.style.overflowY = 'auto';
+ });
+ } else {
+ container.style.overflowY = 'auto';
+ }
+
+ addClass(document.documentElement, swalClasses.shown);
+ addClass(document.body, swalClasses.shown);
+ addClass(container, swalClasses.shown);
+ fixScrollbar();
+ iOSfix();
+ states.previousActiveElement = document.activeElement;
+ if (onComplete !== null && typeof onComplete === 'function') {
+ setTimeout(function () {
+ onComplete(modal);
+ });
+ }
+};
+
+var fixScrollbar = function fixScrollbar() {
+ // for queues, do not do this more than once
+ if (states.previousBodyPadding !== null) {
+ return;
+ }
+ // if the body has overflow
+ if (document.body.scrollHeight > window.innerHeight) {
+ // add padding so the content doesn't shift after removal of scrollbar
+ states.previousBodyPadding = document.body.style.paddingRight;
+ document.body.style.paddingRight = measureScrollbar() + 'px';
+ }
+};
+
+var undoScrollbar = function undoScrollbar() {
+ if (states.previousBodyPadding !== null) {
+ document.body.style.paddingRight = states.previousBodyPadding;
+ states.previousBodyPadding = null;
+ }
+};
+
+// Fix iOS scrolling http://stackoverflow.com/q/39626302/1331425
+var iOSfix = function iOSfix() {
+ var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
+ if (iOS && !hasClass(document.body, swalClasses.iosfix)) {
+ var offset = document.body.scrollTop;
+ document.body.style.top = offset * -1 + 'px';
+ addClass(document.body, swalClasses.iosfix);
+ }
+};
+
+var undoIOSfix = function undoIOSfix() {
+ if (hasClass(document.body, swalClasses.iosfix)) {
+ var offset = parseInt(document.body.style.top, 10);
+ removeClass(document.body, swalClasses.iosfix);
+ document.body.style.top = '';
+ document.body.scrollTop = offset * -1;
+ }
+};
+
+// SweetAlert entry point
+var sweetAlert = function sweetAlert() {
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ if (args[0] === undefined) {
+ console.error('SweetAlert2 expects at least 1 attribute!');
+ return false;
+ }
+
+ var params = _extends({}, modalParams);
+
+ switch (_typeof(args[0])) {
+ case 'string':
+ params.title = args[0];
+ params.html = args[1];
+ params.type = args[2];
+
+ break;
+
+ case 'object':
+ _extends(params, args[0]);
+ params.extraParams = args[0].extraParams;
+
+ if (params.input === 'email' && params.inputValidator === null) {
+ params.inputValidator = function (email) {
+ return new Promise(function (resolve, reject) {
+ var emailRegex = /^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
+ if (emailRegex.test(email)) {
+ resolve();
+ } else {
+ reject('Invalid email address');
+ }
+ });
+ };
+ }
+
+ if (params.input === 'url' && params.inputValidator === null) {
+ params.inputValidator = function (url) {
+ return new Promise(function (resolve, reject) {
+ // taken from https://stackoverflow.com/a/3809435/1331425
+ var urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)$/;
+ if (urlRegex.test(url)) {
+ resolve();
+ } else {
+ reject('Invalid URL');
+ }
+ });
+ };
+ }
+ break;
+
+ default:
+ console.error('SweetAlert2: Unexpected type of argument! Expected "string" or "object", got ' + _typeof(args[0]));
+ return false;
+ }
+
+ setParameters(params);
+
+ var container = getContainer();
+ var modal = getModal();
+
+ return new Promise(function (resolve, reject) {
+ // Close on timer
+ if (params.timer) {
+ modal.timeout = setTimeout(function () {
+ sweetAlert.closeModal(params.onClose);
+ if (params.useRejections) {
+ reject('timer');
+ } else {
+ resolve({ dismiss: 'timer' });
+ }
+ }, params.timer);
+ }
+
+ // Get input element by specified type or, if type isn't specified, by params.input
+ var getInput = function getInput(inputType) {
+ inputType = inputType || params.input;
+ if (!inputType) {
+ return null;
+ }
+ switch (inputType) {
+ case 'select':
+ case 'textarea':
+ case 'file':
+ return getChildByClass(modal, swalClasses[inputType]);
+ case 'checkbox':
+ return modal.querySelector('.' + swalClasses.checkbox + ' input');
+ case 'radio':
+ return modal.querySelector('.' + swalClasses.radio + ' input:checked') || modal.querySelector('.' + swalClasses.radio + ' input:first-child');
+ case 'range':
+ return modal.querySelector('.' + swalClasses.range + ' input');
+ default:
+ return getChildByClass(modal, swalClasses.input);
+ }
+ };
+
+ // Get the value of the modal input
+ var getInputValue = function getInputValue() {
+ var input = getInput();
+ if (!input) {
+ return null;
+ }
+ switch (params.input) {
+ case 'checkbox':
+ return input.checked ? 1 : 0;
+ case 'radio':
+ return input.checked ? input.value : null;
+ case 'file':
+ return input.files.length ? input.files[0] : null;
+ default:
+ return params.inputAutoTrim ? input.value.trim() : input.value;
+ }
+ };
+
+ // input autofocus
+ if (params.input) {
+ setTimeout(function () {
+ var input = getInput();
+ if (input) {
+ focusInput(input);
+ }
+ }, 0);
+ }
+
+ var confirm = function confirm(value) {
+ if (params.showLoaderOnConfirm) {
+ sweetAlert.showLoading();
+ }
+
+ if (params.preConfirm) {
+ params.preConfirm(value, params.extraParams).then(function (preConfirmValue) {
+ sweetAlert.closeModal(params.onClose);
+ resolve(preConfirmValue || value);
+ }, function (error) {
+ sweetAlert.hideLoading();
+ if (error) {
+ sweetAlert.showValidationError(error);
+ }
+ });
+ } else {
+ sweetAlert.closeModal(params.onClose);
+ if (params.useRejections) {
+ resolve(value);
+ } else {
+ resolve({ value: value });
+ }
+ }
+ };
+
+ // Mouse interactions
+ var onButtonEvent = function onButtonEvent(event) {
+ var e = event || window.event;
+ var target = e.target || e.srcElement;
+ var confirmButton = getConfirmButton();
+ var cancelButton = getCancelButton();
+ var targetedConfirm = confirmButton && (confirmButton === target || confirmButton.contains(target));
+ var targetedCancel = cancelButton && (cancelButton === target || cancelButton.contains(target));
+
+ switch (e.type) {
+ case 'mouseover':
+ case 'mouseup':
+ if (params.buttonsStyling) {
+ if (targetedConfirm) {
+ confirmButton.style.backgroundColor = colorLuminance(params.confirmButtonColor, -0.1);
+ } else if (targetedCancel) {
+ cancelButton.style.backgroundColor = colorLuminance(params.cancelButtonColor, -0.1);
+ }
+ }
+ break;
+ case 'mouseout':
+ if (params.buttonsStyling) {
+ if (targetedConfirm) {
+ confirmButton.style.backgroundColor = params.confirmButtonColor;
+ } else if (targetedCancel) {
+ cancelButton.style.backgroundColor = params.cancelButtonColor;
+ }
+ }
+ break;
+ case 'mousedown':
+ if (params.buttonsStyling) {
+ if (targetedConfirm) {
+ confirmButton.style.backgroundColor = colorLuminance(params.confirmButtonColor, -0.2);
+ } else if (targetedCancel) {
+ cancelButton.style.backgroundColor = colorLuminance(params.cancelButtonColor, -0.2);
+ }
+ }
+ break;
+ case 'click':
+ // Clicked 'confirm'
+ if (targetedConfirm && sweetAlert.isVisible()) {
+ sweetAlert.disableButtons();
+ if (params.input) {
+ var inputValue = getInputValue();
+
+ if (params.inputValidator) {
+ sweetAlert.disableInput();
+ params.inputValidator(inputValue, params.extraParams).then(function () {
+ sweetAlert.enableButtons();
+ sweetAlert.enableInput();
+ confirm(inputValue);
+ }, function (error) {
+ sweetAlert.enableButtons();
+ sweetAlert.enableInput();
+ if (error) {
+ sweetAlert.showValidationError(error);
+ }
+ });
+ } else {
+ confirm(inputValue);
+ }
+ } else {
+ confirm(true);
+ }
+
+ // Clicked 'cancel'
+ } else if (targetedCancel && sweetAlert.isVisible()) {
+ sweetAlert.disableButtons();
+ sweetAlert.closeModal(params.onClose);
+ if (params.useRejections) {
+ reject('cancel');
+ } else {
+ resolve({ dismiss: 'cancel' });
+ }
+ }
+ break;
+ default:
+ }
+ };
+
+ var buttons = modal.querySelectorAll('button');
+ for (var i = 0; i < buttons.length; i++) {
+ buttons[i].onclick = onButtonEvent;
+ buttons[i].onmouseover = onButtonEvent;
+ buttons[i].onmouseout = onButtonEvent;
+ buttons[i].onmousedown = onButtonEvent;
+ }
+
+ // Closing modal by close button
+ getCloseButton().onclick = function () {
+ sweetAlert.closeModal(params.onClose);
+ if (params.useRejections) {
+ reject('close');
+ } else {
+ resolve({ dismiss: 'close' });
+ }
+ };
+
+ // Closing modal by overlay click
+ container.onclick = function (e) {
+ if (e.target !== container) {
+ return;
+ }
+ if (params.allowOutsideClick) {
+ sweetAlert.closeModal(params.onClose);
+ if (params.useRejections) {
+ reject('overlay');
+ } else {
+ resolve({ dismiss: 'overlay' });
+ }
+ }
+ };
+
+ var buttonsWrapper = getButtonsWrapper();
+ var confirmButton = getConfirmButton();
+ var cancelButton = getCancelButton();
+
+ // Reverse buttons (Confirm on the right side)
+ if (params.reverseButtons) {
+ confirmButton.parentNode.insertBefore(cancelButton, confirmButton);
+ } else {
+ confirmButton.parentNode.insertBefore(confirmButton, cancelButton);
+ }
+
+ // Focus handling
+ var setFocus = function setFocus(index, increment) {
+ var focusableElements = getFocusableElements(params.focusCancel);
+ // search for visible elements and select the next possible match
+ for (var _i3 = 0; _i3 < focusableElements.length; _i3++) {
+ index = index + increment;
+
+ // rollover to first item
+ if (index === focusableElements.length) {
+ index = 0;
+
+ // go to last item
+ } else if (index === -1) {
+ index = focusableElements.length - 1;
+ }
+
+ // determine if element is visible
+ var el = focusableElements[index];
+ if (isVisible(el)) {
+ return el.focus();
+ }
+ }
+ };
+
+ var handleKeyDown = function handleKeyDown(event) {
+ var e = event || window.event;
+ var keyCode = e.keyCode || e.which;
+
+ if ([9, 13, 32, 27, 37, 38, 39, 40].indexOf(keyCode) === -1) {
+ // Don't do work on keys we don't care about.
+ return;
+ }
+
+ var targetElement = e.target || e.srcElement;
+
+ var focusableElements = getFocusableElements(params.focusCancel);
+ var btnIndex = -1; // Find the button - note, this is a nodelist, not an array.
+ for (var _i4 = 0; _i4 < focusableElements.length; _i4++) {
+ if (targetElement === focusableElements[_i4]) {
+ btnIndex = _i4;
+ break;
+ }
+ }
+
+ // TAB
+ if (keyCode === 9) {
+ if (!e.shiftKey) {
+ // Cycle to the next button
+ setFocus(btnIndex, 1);
+ } else {
+ // Cycle to the prev button
+ setFocus(btnIndex, -1);
+ }
+ e.stopPropagation();
+ e.preventDefault();
+
+ // ARROWS - switch focus between buttons
+ } else if (keyCode === 37 || keyCode === 38 || keyCode === 39 || keyCode === 40) {
+ // focus Cancel button if Confirm button is currently focused
+ if (document.activeElement === confirmButton && isVisible(cancelButton)) {
+ cancelButton.focus();
+ // and vice versa
+ } else if (document.activeElement === cancelButton && isVisible(confirmButton)) {
+ confirmButton.focus();
+ }
+
+ // ENTER/SPACE
+ } else if (keyCode === 13 || keyCode === 32) {
+ if (btnIndex === -1 && params.allowEnterKey) {
+ // ENTER/SPACE clicked outside of a button.
+ if (params.focusCancel) {
+ fireClick(cancelButton, e);
+ } else {
+ fireClick(confirmButton, e);
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ // ESC
+ } else if (keyCode === 27 && params.allowEscapeKey === true) {
+ sweetAlert.closeModal(params.onClose);
+ if (params.useRejections) {
+ reject('esc');
+ } else {
+ resolve({ dismiss: 'esc' });
+ }
+ }
+ };
+
+ if (!window.onkeydown || window.onkeydown.toString() !== handleKeyDown.toString()) {
+ states.previousWindowKeyDown = window.onkeydown;
+ window.onkeydown = handleKeyDown;
+ }
+
+ // Loading state
+ if (params.buttonsStyling) {
+ confirmButton.style.borderLeftColor = params.confirmButtonColor;
+ confirmButton.style.borderRightColor = params.confirmButtonColor;
+ }
+
+ /**
+ * Show spinner instead of Confirm button and disable Cancel button
+ */
+ sweetAlert.hideLoading = sweetAlert.disableLoading = function () {
+ if (!params.showConfirmButton) {
+ hide(confirmButton);
+ if (!params.showCancelButton) {
+ hide(getButtonsWrapper());
+ }
+ }
+ removeClass(buttonsWrapper, swalClasses.loading);
+ removeClass(modal, swalClasses.loading);
+ confirmButton.disabled = false;
+ cancelButton.disabled = false;
+ };
+
+ sweetAlert.getTitle = function () {
+ return getTitle();
+ };
+ sweetAlert.getContent = function () {
+ return getContent();
+ };
+ sweetAlert.getInput = function () {
+ return getInput();
+ };
+ sweetAlert.getImage = function () {
+ return getImage();
+ };
+ sweetAlert.getButtonsWrapper = function () {
+ return getButtonsWrapper();
+ };
+ sweetAlert.getConfirmButton = function () {
+ return getConfirmButton();
+ };
+ sweetAlert.getCancelButton = function () {
+ return getCancelButton();
+ };
+
+ sweetAlert.enableButtons = function () {
+ confirmButton.disabled = false;
+ cancelButton.disabled = false;
+ };
+
+ sweetAlert.disableButtons = function () {
+ confirmButton.disabled = true;
+ cancelButton.disabled = true;
+ };
+
+ sweetAlert.enableConfirmButton = function () {
+ confirmButton.disabled = false;
+ };
+
+ sweetAlert.disableConfirmButton = function () {
+ confirmButton.disabled = true;
+ };
+
+ sweetAlert.enableInput = function () {
+ var input = getInput();
+ if (!input) {
+ return false;
+ }
+ if (input.type === 'radio') {
+ var radiosContainer = input.parentNode.parentNode;
+ var radios = radiosContainer.querySelectorAll('input');
+ for (var _i5 = 0; _i5 < radios.length; _i5++) {
+ radios[_i5].disabled = false;
+ }
+ } else {
+ input.disabled = false;
+ }
+ };
+
+ sweetAlert.disableInput = function () {
+ var input = getInput();
+ if (!input) {
+ return false;
+ }
+ if (input && input.type === 'radio') {
+ var radiosContainer = input.parentNode.parentNode;
+ var radios = radiosContainer.querySelectorAll('input');
+ for (var _i6 = 0; _i6 < radios.length; _i6++) {
+ radios[_i6].disabled = true;
+ }
+ } else {
+ input.disabled = true;
+ }
+ };
+
+ // Set modal min-height to disable scrolling inside the modal
+ sweetAlert.recalculateHeight = debounce(function () {
+ var modal = getModal();
+ if (!modal) {
+ return;
+ }
+ var prevState = modal.style.display;
+ modal.style.minHeight = '';
+ show(modal);
+ modal.style.minHeight = modal.scrollHeight + 1 + 'px';
+ modal.style.display = prevState;
+ }, 50);
+
+ // Show block with validation error
+ sweetAlert.showValidationError = function (error) {
+ var validationError = getValidationError();
+ validationError.innerHTML = error;
+ show(validationError);
+
+ var input = getInput();
+ if (input) {
+ focusInput(input);
+ addClass(input, swalClasses.inputerror);
+ }
+ };
+
+ // Hide block with validation error
+ sweetAlert.resetValidationError = function () {
+ var validationError = getValidationError();
+ hide(validationError);
+ sweetAlert.recalculateHeight();
+
+ var input = getInput();
+ if (input) {
+ removeClass(input, swalClasses.inputerror);
+ }
+ };
+
+ sweetAlert.getProgressSteps = function () {
+ return params.progressSteps;
+ };
+
+ sweetAlert.setProgressSteps = function (progressSteps) {
+ params.progressSteps = progressSteps;
+ setParameters(params);
+ };
+
+ sweetAlert.showProgressSteps = function () {
+ show(getProgressSteps());
+ };
+
+ sweetAlert.hideProgressSteps = function () {
+ hide(getProgressSteps());
+ };
+
+ sweetAlert.enableButtons();
+ sweetAlert.hideLoading();
+ sweetAlert.resetValidationError();
+
+ // inputs
+ var inputTypes = ['input', 'file', 'range', 'select', 'radio', 'checkbox', 'textarea'];
+ var input = void 0;
+ for (var _i7 = 0; _i7 < inputTypes.length; _i7++) {
+ var inputClass = swalClasses[inputTypes[_i7]];
+ var inputContainer = getChildByClass(modal, inputClass);
+ input = getInput(inputTypes[_i7]);
+
+ // set attributes
+ if (input) {
+ for (var j in input.attributes) {
+ if (input.attributes.hasOwnProperty(j)) {
+ var attrName = input.attributes[j].name;
+ if (attrName !== 'type' && attrName !== 'value') {
+ input.removeAttribute(attrName);
+ }
+ }
+ }
+ for (var attr in params.inputAttributes) {
+ input.setAttribute(attr, params.inputAttributes[attr]);
+ }
+ }
+
+ // set class
+ inputContainer.className = inputClass;
+ if (params.inputClass) {
+ addClass(inputContainer, params.inputClass);
+ }
+
+ hide(inputContainer);
+ }
+
+ var populateInputOptions = void 0;
+ switch (params.input) {
+ case 'text':
+ case 'email':
+ case 'password':
+ case 'number':
+ case 'tel':
+ case 'url':
+ input = getChildByClass(modal, swalClasses.input);
+ input.value = params.inputValue;
+ input.placeholder = params.inputPlaceholder;
+ input.type = params.input;
+ show(input);
+ break;
+ case 'file':
+ input = getChildByClass(modal, swalClasses.file);
+ input.placeholder = params.inputPlaceholder;
+ input.type = params.input;
+ show(input);
+ break;
+ case 'range':
+ var range = getChildByClass(modal, swalClasses.range);
+ var rangeInput = range.querySelector('input');
+ var rangeOutput = range.querySelector('output');
+ rangeInput.value = params.inputValue;
+ rangeInput.type = params.input;
+ rangeOutput.value = params.inputValue;
+ show(range);
+ break;
+ case 'select':
+ var select = getChildByClass(modal, swalClasses.select);
+ select.innerHTML = '';
+ if (params.inputPlaceholder) {
+ var placeholder = document.createElement('option');
+ placeholder.innerHTML = params.inputPlaceholder;
+ placeholder.value = '';
+ placeholder.disabled = true;
+ placeholder.selected = true;
+ select.appendChild(placeholder);
+ }
+ populateInputOptions = function populateInputOptions(inputOptions) {
+ for (var optionValue in inputOptions) {
+ var option = document.createElement('option');
+ option.value = optionValue;
+ option.innerHTML = inputOptions[optionValue];
+ if (params.inputValue === optionValue) {
+ option.selected = true;
+ }
+ select.appendChild(option);
+ }
+ show(select);
+ select.focus();
+ };
+ break;
+ case 'radio':
+ var radio = getChildByClass(modal, swalClasses.radio);
+ radio.innerHTML = '';
+ populateInputOptions = function populateInputOptions(inputOptions) {
+ for (var radioValue in inputOptions) {
+ var radioInput = document.createElement('input');
+ var radioLabel = document.createElement('label');
+ var radioLabelSpan = document.createElement('span');
+ radioInput.type = 'radio';
+ radioInput.name = swalClasses.radio;
+ radioInput.value = radioValue;
+ if (params.inputValue === radioValue) {
+ radioInput.checked = true;
+ }
+ radioLabelSpan.innerHTML = inputOptions[radioValue];
+ radioLabel.appendChild(radioInput);
+ radioLabel.appendChild(radioLabelSpan);
+ radioLabel.for = radioInput.id;
+ radio.appendChild(radioLabel);
+ }
+ show(radio);
+ var radios = radio.querySelectorAll('input');
+ if (radios.length) {
+ radios[0].focus();
+ }
+ };
+ break;
+ case 'checkbox':
+ var checkbox = getChildByClass(modal, swalClasses.checkbox);
+ var checkboxInput = getInput('checkbox');
+ checkboxInput.type = 'checkbox';
+ checkboxInput.value = 1;
+ checkboxInput.id = swalClasses.checkbox;
+ checkboxInput.checked = Boolean(params.inputValue);
+ var label = checkbox.getElementsByTagName('span');
+ if (label.length) {
+ checkbox.removeChild(label[0]);
+ }
+ label = document.createElement('span');
+ label.innerHTML = params.inputPlaceholder;
+ checkbox.appendChild(label);
+ show(checkbox);
+ break;
+ case 'textarea':
+ var textarea = getChildByClass(modal, swalClasses.textarea);
+ textarea.value = params.inputValue;
+ textarea.placeholder = params.inputPlaceholder;
+ show(textarea);
+ break;
+ case null:
+ break;
+ default:
+ console.error('SweetAlert2: Unexpected type of input! Expected "text", "email", "password", "number", "tel", "select", "radio", "checkbox", "textarea", "file" or "url", got "' + params.input + '"');
+ break;
+ }
+
+ if (params.input === 'select' || params.input === 'radio') {
+ if (params.inputOptions instanceof Promise) {
+ sweetAlert.showLoading();
+ params.inputOptions.then(function (inputOptions) {
+ sweetAlert.hideLoading();
+ populateInputOptions(inputOptions);
+ });
+ } else if (_typeof(params.inputOptions) === 'object') {
+ populateInputOptions(params.inputOptions);
+ } else {
+ console.error('SweetAlert2: Unexpected type of inputOptions! Expected object or Promise, got ' + _typeof(params.inputOptions));
+ }
+ }
+
+ openModal(params.animation, params.onOpen);
+
+ // Focus the first element (input or button)
+ if (params.allowEnterKey) {
+ setFocus(-1, 1);
+ } else {
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ }
+
+ // fix scroll
+ getContainer().scrollTop = 0;
+
+ // Observe changes inside the modal and adjust height
+ if (typeof MutationObserver !== 'undefined' && !swal2Observer) {
+ swal2Observer = new MutationObserver(sweetAlert.recalculateHeight);
+ swal2Observer.observe(modal, { childList: true, characterData: true, subtree: true });
+ }
+ });
+};
+
+/*
+ * Global function to determine if swal2 modal is shown
+ */
+sweetAlert.isVisible = function () {
+ return !!getModal();
+};
+
+/*
+ * Global function for chaining sweetAlert modals
+ */
+sweetAlert.queue = function (steps) {
+ queue = steps;
+ var resetQueue = function resetQueue() {
+ queue = [];
+ document.body.removeAttribute('data-swal2-queue-step');
+ };
+ var queueResult = [];
+ return new Promise(function (resolve, reject) {
+ (function step(i, callback) {
+ if (i < queue.length) {
+ document.body.setAttribute('data-swal2-queue-step', i);
+
+ sweetAlert(queue[i]).then(function (result) {
+ queueResult.push(result);
+ step(i + 1, callback);
+ }, function (dismiss) {
+ resetQueue();
+ reject(dismiss);
+ });
+ } else {
+ resetQueue();
+ resolve(queueResult);
+ }
+ })(0);
+ });
+};
+
+/*
+ * Global function for getting the index of current modal in queue
+ */
+sweetAlert.getQueueStep = function () {
+ return document.body.getAttribute('data-swal2-queue-step');
+};
+
+/*
+ * Global function for inserting a modal to the queue
+ */
+sweetAlert.insertQueueStep = function (step, index) {
+ if (index && index < queue.length) {
+ return queue.splice(index, 0, step);
+ }
+ return queue.push(step);
+};
+
+/*
+ * Global function for deleting a modal from the queue
+ */
+sweetAlert.deleteQueueStep = function (index) {
+ if (typeof queue[index] !== 'undefined') {
+ queue.splice(index, 1);
+ }
+};
+
+/*
+ * Global function to close sweetAlert
+ */
+sweetAlert.close = sweetAlert.closeModal = function (onComplete) {
+ var container = getContainer();
+ var modal = getModal();
+ if (!modal) {
+ return;
+ }
+ removeClass(modal, swalClasses.show);
+ addClass(modal, swalClasses.hide);
+ clearTimeout(modal.timeout);
+
+ resetPrevState();
+
+ var removeModalAndResetState = function removeModalAndResetState() {
+ if (container.parentNode) {
+ container.parentNode.removeChild(container);
+ }
+ removeClass(document.documentElement, swalClasses.shown);
+ removeClass(document.body, swalClasses.shown);
+ undoScrollbar();
+ undoIOSfix();
+ };
+
+ // If animation is supported, animate
+ if (animationEndEvent && !hasClass(modal, swalClasses.noanimation)) {
+ modal.addEventListener(animationEndEvent, function swalCloseEventFinished() {
+ modal.removeEventListener(animationEndEvent, swalCloseEventFinished);
+ if (hasClass(modal, swalClasses.hide)) {
+ removeModalAndResetState();
+ }
+ });
+ } else {
+ // Otherwise, remove immediately
+ removeModalAndResetState();
+ }
+ if (onComplete !== null && typeof onComplete === 'function') {
+ setTimeout(function () {
+ onComplete(modal);
+ });
+ }
+};
+
+/*
+ * Global function to click 'Confirm' button
+ */
+sweetAlert.clickConfirm = function () {
+ return getConfirmButton().click();
+};
+
+/*
+ * Global function to click 'Cancel' button
+ */
+sweetAlert.clickCancel = function () {
+ return getCancelButton().click();
+};
+
+/**
+ * Show spinner instead of Confirm button and disable Cancel button
+ */
+sweetAlert.showLoading = sweetAlert.enableLoading = function () {
+ var modal = getModal();
+ if (!modal) {
+ sweetAlert('');
+ }
+ var buttonsWrapper = getButtonsWrapper();
+ var confirmButton = getConfirmButton();
+ var cancelButton = getCancelButton();
+
+ show(buttonsWrapper);
+ show(confirmButton, 'inline-block');
+ addClass(buttonsWrapper, swalClasses.loading);
+ addClass(modal, swalClasses.loading);
+ confirmButton.disabled = true;
+ cancelButton.disabled = true;
+};
+
+/**
+ * Set default params for each popup
+ * @param {Object} userParams
+ */
+sweetAlert.setDefaults = function (userParams) {
+ if (!userParams || (typeof userParams === 'undefined' ? 'undefined' : _typeof(userParams)) !== 'object') {
+ return console.error('SweetAlert2: the argument for setDefaults() is required and has to be a object');
+ }
+
+ for (var param in userParams) {
+ if (!defaultParams.hasOwnProperty(param) && param !== 'extraParams') {
+ console.warn('SweetAlert2: Unknown parameter "' + param + '"');
+ delete userParams[param];
+ }
+ }
+
+ _extends(modalParams, userParams);
+};
+
+/**
+ * Reset default params for each popup
+ */
+sweetAlert.resetDefaults = function () {
+ modalParams = _extends({}, defaultParams);
+};
+
+sweetAlert.noop = function () {};
+
+sweetAlert.version = '6.6.6';
+
+sweetAlert.default = sweetAlert;
+
+return sweetAlert;
+
+})));
+if (window.Sweetalert2) window.sweetAlert = window.swal = window.Sweetalert2;
diff --git a/wp-content/plugins/imagify/assets/js/sweetalert2.min.js b/wp-content/plugins/imagify/assets/js/sweetalert2.min.js
new file mode 100644
index 00000000..bdfb8817
--- /dev/null
+++ b/wp-content/plugins/imagify/assets/js/sweetalert2.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Sweetalert2=t()}(this,function(){"use strict";var e={title:"",titleText:"",text:"",html:"",type:null,customClass:"",target:"body",animation:!0,allowOutsideClick:!0,allowEscapeKey:!0,allowEnterKey:!0,showConfirmButton:!0,showCancelButton:!1,preConfirm:null,confirmButtonText:"OK",confirmButtonColor:"#3085d6",confirmButtonClass:null,cancelButtonText:"Cancel",cancelButtonColor:"#aaa",cancelButtonClass:null,buttonsStyling:!0,reverseButtons:!1,focusCancel:!1,showCloseButton:!1,showLoaderOnConfirm:!1,imageUrl:null,imageWidth:null,imageHeight:null,imageClass:null,timer:null,width:500,padding:20,background:"#fff",input:null,inputPlaceholder:"",inputValue:"",inputOptions:{},inputAutoTrim:!0,inputClass:null,inputAttributes:{},inputValidator:null,progressSteps:[],currentProgressStep:null,progressStepsDistance:"40px",onOpen:null,onClose:null,useRejections:!0},t=function(e){var t={};for(var n in e)t[e[n]]="swal2-"+e[n];return t},n=t(["container","shown","iosfix","modal","overlay","fade","show","hide","noanimation","close","title","content","buttonswrapper","confirm","cancel","icon","image","input","file","range","select","radio","checkbox","textarea","inputerror","validationerror","progresssteps","activeprogressstep","progresscircle","progressline","loading","styled"]),o=t(["success","warning","info","question","error"]),r=function(e,t){(e=String(e).replace(/[^0-9a-f]/gi,"")).length<6&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),t=t||0;for(var n="#",o=0;o<3;o++){var r=parseInt(e.substr(2*o,2),16);n+=("00"+(r=Math.round(Math.min(Math.max(0,r+r*t),255)).toString(16))).substr(r.length)}return n},i=function(e){var t=[];for(var n in e)-1===t.indexOf(e[n])&&t.push(e[n]);return t},a={previousWindowKeyDown:null,previousActiveElement:null,previousBodyPadding:null},l=function(e){if("undefined"!=typeof document){var t=document.createElement("div");t.className=n.container,t.innerHTML=s;var o=document.querySelector(e.target);o||(console.warn("SweetAlert2: Can't find the target \""+e.target+'"'),o=document.body),o.appendChild(t);var r=c(),i=A(r,n.input),a=A(r,n.file),l=r.querySelector("."+n.range+" input"),u=r.querySelector("."+n.range+" output"),d=A(r,n.select),p=r.querySelector("."+n.checkbox+" input"),f=A(r,n.textarea);return i.oninput=function(){$.resetValidationError()},i.onkeydown=function(t){setTimeout(function(){13===t.keyCode&&e.allowEnterKey&&(t.stopPropagation(),$.clickConfirm())},0)},a.onchange=function(){$.resetValidationError()},l.oninput=function(){$.resetValidationError(),u.value=l.value},l.onchange=function(){$.resetValidationError(),l.previousSibling.value=l.value},d.onchange=function(){$.resetValidationError()},p.onchange=function(){$.resetValidationError()},f.oninput=function(){$.resetValidationError()},r}console.error("SweetAlert2 requires document to initialize")},s=('\n \n
\n
\n \n
\n
?
\n
!
\n
i
\n
\n
\n
\n
\n
\n
\n
\n \n \n
\n
\n
\n
\n \n \n
\n
\n
\n OK \n Cancel \n
\n
à \n
\n').replace(/(^|\n)\s*/g,""),u=function(){return document.body.querySelector("."+n.container)},c=function(){return u()?u().querySelector("."+n.modal):null},d=function(){return c().querySelectorAll("."+n.icon)},p=function(e){return u()?u().querySelector("."+e):null},f=function(){return p(n.title)},m=function(){return p(n.content)},v=function(){return p(n.image)},h=function(){return p(n.buttonswrapper)},g=function(){return p(n.progresssteps)},y=function(){return p(n.validationerror)},b=function(){return p(n.confirm)},w=function(){return p(n.cancel)},C=function(){return p(n.close)},k=function(e){var t=[b(),w()];e&&t.reverse();var n=t.concat(Array.prototype.slice.call(c().querySelectorAll('button, input:not([type=hidden]), textarea, select, a, *[tabindex]:not([tabindex="-1"])')));return i(n)},x=function(e,t){return!!e.classList&&e.classList.contains(t)},S=function(e){if(e.focus(),"file"!==e.type){var t=e.value;e.value="",e.value=t}},E=function(e,t){e&&t&&t.split(/\s+/).filter(Boolean).forEach(function(t){e.classList.add(t)})},B=function(e,t){e&&t&&t.split(/\s+/).filter(Boolean).forEach(function(t){e.classList.remove(t)})},A=function(e,t){for(var n=0;n "),t.text||t.html){if("object"===R(t.html))if(p.innerHTML="",0 in t.html)for(var A=0;A in t.html;A++)p.appendChild(t.html[A].cloneNode(!0));else p.appendChild(t.html.cloneNode(!0));else t.html?p.innerHTML=t.html:t.text&&(p.textContent=t.text);P(p)}else T(p);t.showCloseButton?P(S):T(S),r.className=n.modal,t.customClass&&E(r,t.customClass);var M=g(),V=parseInt(null===t.currentProgressStep?$.getQueueStep():t.currentProgressStep,10);t.progressSteps.length?(P(M),L(M),V>=t.progressSteps.length&&console.warn("SweetAlert2: Invalid currentProgressStep parameter, it should be less than progressSteps.length (currentProgressStep like JS arrays starts from 0)"),t.progressSteps.forEach(function(e,o){var r=document.createElement("li");if(E(r,n.progresscircle),r.innerHTML=e,o===V&&E(r,n.activeprogressstep),M.appendChild(r),o!==t.progressSteps.length-1){var i=document.createElement("li");E(i,n.progressline),i.style.width=t.progressStepsDistance,M.appendChild(i)}})):T(M);for(var O=d(),H=0;Hwindow.innerHeight&&(a.previousBodyPadding=document.body.style.paddingRight,document.body.style.paddingRight=N()+"px")},Q=function(){null!==a.previousBodyPadding&&(document.body.style.paddingRight=a.previousBodyPadding,a.previousBodyPadding=null)},Y=function(){if(/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream&&!x(document.body,n.iosfix)){var e=document.body.scrollTop;document.body.style.top=-1*e+"px",E(document.body,n.iosfix)}},_=function(){if(x(document.body,n.iosfix)){var e=parseInt(document.body.style.top,10);B(document.body,n.iosfix),document.body.style.top="",document.body.scrollTop=-1*e}},$=function e(){for(var t=arguments.length,o=Array(t),i=0;iget_auth_url( $args['url'] );
+ }
+
+ return $args;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Bulk/AbstractBulk.php b/wp-content/plugins/imagify/classes/Bulk/AbstractBulk.php
new file mode 100644
index 00000000..017e0971
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Bulk/AbstractBulk.php
@@ -0,0 +1,88 @@
+filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Format context data (stats).
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $data {
+ * The data to format.
+ *
+ * @type int $count-optimized Number of media optimized.
+ * @type int $count-errors Number of media having an optimization error.
+ * @type int $optimized-size Optimized filesize.
+ * @type int $original-size Original filesize.
+ * @type string $errors_url URL to the page listing the optimization errors.
+ * }
+ * @return array {
+ * The formated data.
+ *
+ * @type string $count-optimized Number of media optimized.
+ * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors.
+ * @type string $optimized-size Optimized filesize.
+ * @type string $original-size Original filesize.
+ * }
+ */
+ protected function format_context_data( $data ) {
+ /* translators: %s is a formatted number, dont use %d. */
+ $data['count-optimized'] = sprintf( _n( '%s Media File Optimized', '%s Media Files Optimized', $data['count-optimized'], 'imagify' ), '' . number_format_i18n( $data['count-optimized'] ) . ' ' );
+
+ if ( $data['count-errors'] ) {
+ /* translators: %s is a formatted number, dont use %d. */
+ $data['count-errors'] = sprintf( _n( '%s Error', '%s Errors', $data['count-errors'], 'imagify' ), '' . number_format_i18n( $data['count-errors'] ) . ' ' );
+ $data['count-errors'] .= ' ' . __( 'View Errors', 'imagify' ) . ' ';
+ } else {
+ $data['count-errors'] = '';
+ }
+
+ if ( $data['optimized-size'] ) {
+ $data['optimized-size'] = '' . __( 'Optimized Filesize', 'imagify' ) . ' ' . imagify_size_format( $data['optimized-size'], 2 );
+ } else {
+ $data['optimized'] = '';
+ }
+
+ if ( $data['original-size'] ) {
+ $data['original-size'] = '' . __( 'Original Filesize', 'imagify' ) . ' ' . imagify_size_format( $data['original-size'], 2 );
+ } else {
+ $data['original-size'] = '';
+ }
+
+ unset( $data['errors_url'] );
+
+ return $data;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Bulk/BulkInterface.php b/wp-content/plugins/imagify/classes/Bulk/BulkInterface.php
new file mode 100644
index 00000000..7aabca27
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Bulk/BulkInterface.php
@@ -0,0 +1,73 @@
+ true,
+ ) );
+
+ if ( ! $folders ) {
+ return [];
+ }
+
+ /**
+ * Triggered before getting file IDs.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $folders An array of folders data.
+ * @param int $optimization_level The optimization level that will be used for the optimization.
+ */
+ do_action( 'imagify_bulk_optimize_files_before_get_files', $folders, $optimization_level );
+
+ /**
+ * Get the files from DB, and from the folders.
+ */
+ $files = \Imagify_Custom_Folders::get_files_from_folders( $folders, [
+ 'optimization_level' => $optimization_level,
+ ] );
+
+ if ( ! $files ) {
+ return [];
+ }
+
+ // We need to output file URLs.
+ foreach ( $files as $k => $file ) {
+ $files[ $k ] = esc_url( \Imagify_Files_Scan::remove_placeholder( $file['path'], 'url' ) );
+ }
+
+ return $files;
+ }
+
+ /**
+ * Get ids of all optimized media without webp versions.
+ *
+ * @since 1.9
+ * @since 1.9.5 The method doesn't return the IDs directly anymore.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * @type array $ids A list of media IDs.
+ * @type array $errors {
+ * @type array $no_file_path A list of media IDs.
+ * @type array $no_backup A list of media IDs.
+ * }
+ * }
+ */
+ public function get_optimized_media_ids_without_webp() {
+ global $wpdb;
+
+ @set_time_limit( 0 );
+
+ $files_table = \Imagify_Files_DB::get_instance()->get_table_name();
+ $folders_table = \Imagify_Folders_DB::get_instance()->get_table_name();
+ $mime_types = \Imagify_DB::get_mime_types( 'image' );
+ $webp_suffix = constant( imagify_get_optimization_process_class_name( 'custom-folders' ) . '::WEBP_SUFFIX' );
+ $files = $wpdb->get_results( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT fi.file_id, fi.path
+ FROM $files_table as fi
+ INNER JOIN $folders_table AS fo
+ ON ( fi.folder_id = fo.folder_id )
+ WHERE
+ fi.mime_type IN ( $mime_types )
+ AND ( fi.status = 'success' OR fi.status = 'already_optimized' )
+ AND ( fi.data NOT LIKE %s OR fi.data IS NULL )
+ ORDER BY fi.file_id DESC",
+ '%' . $wpdb->esc_like( $webp_suffix . '";a:4:{s:7:"success";b:1;' ) . '%'
+ ) );
+
+ $wpdb->flush();
+ unset( $mime_types, $files_table, $folders_table, $webp_suffix );
+
+ $data = [
+ 'ids' => [],
+ 'errors' => [
+ 'no_file_path' => [],
+ 'no_backup' => [],
+ ],
+ ];
+
+ if ( ! $files ) {
+ return $data;
+ }
+
+ foreach ( $files as $file ) {
+ $file_id = absint( $file->file_id );
+
+ if ( empty( $file->path ) ) {
+ // Problem.
+ $data['errors']['no_file_path'][] = $file_id;
+ continue;
+ }
+
+ $file_path = \Imagify_Files_Scan::remove_placeholder( $file->path );
+ $backup_path = \Imagify_Custom_Folders::get_file_backup_path( $file_path );
+
+ if ( ! $this->filesystem->exists( $backup_path ) ) {
+ // No backup, no webp.
+ $data['errors']['no_backup'][] = $file_id;
+ continue;
+ }
+
+ $data['ids'][] = $file_id;
+ } // End foreach().
+
+ return $data;
+ }
+
+ /**
+ * Tell if there are optimized media without webp versions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The number of media.
+ */
+ public function has_optimized_media_without_webp() {
+ global $wpdb;
+
+ $files_table = \Imagify_Files_DB::get_instance()->get_table_name();
+ $folders_table = \Imagify_Folders_DB::get_instance()->get_table_name();
+ $mime_types = \Imagify_DB::get_mime_types( 'image' );
+ $webp_suffix = constant( imagify_get_optimization_process_class_name( 'custom-folders' ) . '::WEBP_SUFFIX' );
+
+ return (int) $wpdb->get_var( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT COUNT(fi.file_id)
+ FROM $files_table as fi
+ INNER JOIN $folders_table AS fo
+ ON ( fi.folder_id = fo.folder_id )
+ WHERE
+ fi.mime_type IN ( $mime_types )
+ AND ( fi.status = 'success' OR fi.status = 'already_optimized' )
+ AND ( fi.data NOT LIKE %s OR fi.data IS NULL )",
+ '%' . $wpdb->esc_like( $webp_suffix . '";a:4:{s:7:"success";b:1;' ) . '%'
+ ) );
+ }
+
+ /**
+ * Get the context data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * The formated data.
+ *
+ * @type string $count-optimized Number of media optimized.
+ * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors.
+ * @type string $optimized-size Optimized filesize.
+ * @type string $original-size Original filesize.
+ * }
+ */
+ public function get_context_data() {
+ $data = array(
+ 'count-optimized' => \Imagify_Files_Stats::count_optimized_files(),
+ 'count-errors' => \Imagify_Files_Stats::count_error_files(),
+ 'optimized-size' => \Imagify_Files_Stats::get_optimized_size(),
+ 'original-size' => \Imagify_Files_Stats::get_original_size(),
+ 'errors_url' => get_imagify_admin_url( 'folder-errors', $this->context ),
+ );
+
+ return $this->format_context_data( $data );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Bulk/Noop.php b/wp-content/plugins/imagify/classes/Bulk/Noop.php
new file mode 100644
index 00000000..575e9917
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Bulk/Noop.php
@@ -0,0 +1,94 @@
+ [],
+ 'errors' => [
+ 'no_file_path' => [],
+ 'no_backup' => [],
+ ],
+ ];
+ }
+
+ /**
+ * Tell if there are optimized media without webp versions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The number of media.
+ */
+ public function has_optimized_media_without_webp() {
+ return 0;
+ }
+
+ /**
+ * Get the context data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * The formated data.
+ *
+ * @type string $count-optimized Number of media optimized.
+ * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors.
+ * @type string $optimized-size Optimized filesize.
+ * @type string $original-size Original filesize.
+ * }
+ */
+ public function get_context_data() {
+ $data = [
+ 'count-optimized' => 0,
+ 'count-errors' => 0,
+ 'optimized-size' => 0,
+ 'original-size' => 0,
+ 'errors_url' => get_imagify_admin_url( 'folder-errors', 'noop' ),
+ ];
+
+ return $this->format_context_data( $data );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Bulk/WP.php b/wp-content/plugins/imagify/classes/Bulk/WP.php
new file mode 100644
index 00000000..514941f4
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Bulk/WP.php
@@ -0,0 +1,353 @@
+ true,
+ ] );
+ $ids = $wpdb->get_col( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT DISTINCT p.ID
+ FROM $wpdb->posts AS p
+ $nodata_join
+ LEFT JOIN $wpdb->postmeta AS mt1
+ ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
+ LEFT JOIN $wpdb->postmeta AS mt2
+ ON ( p.ID = mt2.post_id AND mt2.meta_key = '_imagify_optimization_level' )
+ WHERE
+ p.post_mime_type IN ( $mime_types )
+ AND (
+ mt1.meta_value = 'error'
+ OR
+ mt2.meta_value != %d
+ OR
+ mt2.post_id IS NULL
+ )
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ $nodata_where
+ ORDER BY
+ CASE mt1.meta_value
+ WHEN 'already_optimized' THEN 2
+ ELSE 1
+ END ASC,
+ p.ID DESC
+ LIMIT 0, %d",
+ $optimization_level,
+ imagify_get_unoptimized_attachment_limit()
+ ) );
+
+ $wpdb->flush();
+ unset( $mime_types );
+ $ids = array_filter( array_map( 'absint', $ids ) );
+
+ if ( ! $ids ) {
+ return [];
+ }
+
+ $metas = \Imagify_DB::get_metas( [
+ // Get attachments filename.
+ 'filenames' => '_wp_attached_file',
+ // Get attachments data.
+ 'data' => '_imagify_data',
+ // Get attachments optimization level.
+ 'optimization_levels' => '_imagify_optimization_level',
+ // Get attachments status.
+ 'statuses' => '_imagify_status',
+ ], $ids );
+
+ // First run.
+ foreach ( $ids as $i => $id ) {
+ $attachment_status = isset( $metas['statuses'][ $id ] ) ? $metas['statuses'][ $id ] : false;
+ $attachment_optimization_level = isset( $metas['optimization_levels'][ $id ] ) ? $metas['optimization_levels'][ $id ] : false;
+ $attachment_error = '';
+
+ if ( isset( $metas['data'][ $id ]['sizes']['full']['error'] ) ) {
+ $attachment_error = $metas['data'][ $id ]['sizes']['full']['error'];
+ }
+
+ // Don't try to re-optimize if the optimization level is still the same.
+ if ( $optimization_level === $attachment_optimization_level && is_string( $attachment_error ) ) {
+ unset( $ids[ $i ] );
+ continue;
+ }
+
+ // Don't try to re-optimize images already compressed.
+ if ( 'already_optimized' === $attachment_status && $attachment_optimization_level >= $optimization_level ) {
+ unset( $ids[ $i ] );
+ continue;
+ }
+
+ $attachment_error = trim( $attachment_error );
+
+ // Don't try to re-optimize images with an empty error message.
+ if ( 'error' === $attachment_status && empty( $attachment_error ) ) {
+ unset( $ids[ $i ] );
+ }
+ }
+
+ if ( ! $ids ) {
+ return [];
+ }
+
+ $ids = array_values( $ids );
+
+ /**
+ * Triggered before testing for file existence.
+ *
+ * @since 1.6.7
+ * @author Grégory Viguier
+ *
+ * @param array $ids An array of attachment IDs.
+ * @param array $metas An array of the data fetched from the database.
+ * @param int $optimization_level The optimization level that will be used for the optimization.
+ */
+ do_action( 'imagify_bulk_optimize_before_file_existence_tests', $ids, $metas, $optimization_level );
+
+ $data = [];
+
+ foreach ( $ids as $i => $id ) {
+ if ( empty( $metas['filenames'][ $id ] ) ) {
+ // Problem.
+ continue;
+ }
+
+ $file_path = get_imagify_attached_file( $metas['filenames'][ $id ] );
+
+ if ( ! $file_path || ! $this->filesystem->exists( $file_path ) ) {
+ continue;
+ }
+
+ $attachment_backup_path = get_imagify_attachment_backup_path( $file_path );
+ $attachment_status = isset( $metas['statuses'][ $id ] ) ? $metas['statuses'][ $id ] : false;
+ $attachment_optimization_level = isset( $metas['optimization_levels'][ $id ] ) ? $metas['optimization_levels'][ $id ] : false;
+
+ // Don't try to re-optimize if there is no backup file.
+ if ( 'success' === $attachment_status && $optimization_level !== $attachment_optimization_level && ! $this->filesystem->exists( $attachment_backup_path ) ) {
+ continue;
+ }
+
+ $data[ '_' . $id ] = esc_url( get_imagify_attachment_url( $metas['filenames'][ $id ] ) );
+ } // End foreach().
+
+ return $data;
+ }
+
+ /**
+ * Get ids of all optimized media without webp versions.
+ *
+ * @since 1.9
+ * @since 1.9.5 The method doesn't return the IDs directly anymore.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * @type array $ids A list of media IDs.
+ * @type array $errors {
+ * @type array $no_file_path A list of media IDs.
+ * @type array $no_backup A list of media IDs.
+ * }
+ * }
+ */
+ public function get_optimized_media_ids_without_webp() {
+ global $wpdb;
+
+ @set_time_limit( 0 );
+
+ $mime_types = \Imagify_DB::get_mime_types( 'image' );
+ $statuses = \Imagify_DB::get_post_statuses();
+ $nodata_join = \Imagify_DB::get_required_wp_metadata_join_clause();
+ $nodata_where = \Imagify_DB::get_required_wp_metadata_where_clause( [
+ 'prepared' => true,
+ ] );
+ $webp_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::WEBP_SUFFIX' );
+ $ids = $wpdb->get_col( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT p.ID
+ FROM $wpdb->posts AS p
+ $nodata_join
+ LEFT JOIN $wpdb->postmeta AS mt1
+ ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
+ LEFT JOIN $wpdb->postmeta AS mt2
+ ON ( p.ID = mt2.post_id AND mt2.meta_key = '_imagify_data' )
+ WHERE
+ p.post_mime_type IN ( $mime_types )
+ AND ( mt1.meta_value = 'success' OR mt1.meta_value = 'already_optimized' )
+ AND mt2.meta_value NOT LIKE %s
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ $nodata_where
+ ORDER BY p.ID DESC
+ LIMIT 0, %d",
+ '%' . $wpdb->esc_like( $webp_suffix . '";a:4:{s:7:"success";b:1;' ) . '%',
+ imagify_get_unoptimized_attachment_limit()
+ ) );
+
+ $wpdb->flush();
+ unset( $mime_types, $statuses, $webp_suffix );
+
+ $ids = array_filter( array_map( 'absint', $ids ) );
+ $data = [
+ 'ids' => [],
+ 'errors' => [
+ 'no_file_path' => [],
+ 'no_backup' => [],
+ ],
+ ];
+
+ if ( ! $ids ) {
+ return $data;
+ }
+
+ $metas = \Imagify_DB::get_metas( [
+ // Get attachments filename.
+ 'filenames' => '_wp_attached_file',
+ ], $ids );
+
+ /**
+ * Triggered before testing for file existence.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $ids An array of attachment IDs.
+ * @param array $metas An array of the data fetched from the database.
+ * @param string $context The context.
+ */
+ do_action( 'imagify_bulk_generate_webp_before_file_existence_tests', $ids, $metas, 'wp' );
+
+ foreach ( $ids as $i => $id ) {
+ if ( empty( $metas['filenames'][ $id ] ) ) {
+ // Problem. Should not happen, thanks to the wpdb query.
+ $data['errors']['no_file_path'][] = $id;
+ continue;
+ }
+
+ $file_path = get_imagify_attached_file( $metas['filenames'][ $id ] );
+
+ if ( ! $file_path ) {
+ // Main file not found.
+ $data['errors']['no_file_path'][] = $id;
+ continue;
+ }
+
+ $backup_path = get_imagify_attachment_backup_path( $file_path );
+
+ if ( ! $this->filesystem->exists( $backup_path ) ) {
+ // No backup, no webp.
+ $data['errors']['no_backup'][] = $id;
+ continue;
+ }
+
+ $data['ids'][] = $id;
+ } // End foreach().
+
+ return $data;
+ }
+
+ /**
+ * Tell if there are optimized media without webp versions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The number of media.
+ */
+ public function has_optimized_media_without_webp() {
+ global $wpdb;
+
+ $mime_types = \Imagify_DB::get_mime_types( 'image' );
+ $statuses = \Imagify_DB::get_post_statuses();
+ $nodata_join = \Imagify_DB::get_required_wp_metadata_join_clause();
+ $nodata_where = \Imagify_DB::get_required_wp_metadata_where_clause( [
+ 'prepared' => true,
+ ] );
+ $webp_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::WEBP_SUFFIX' );
+
+ return (int) $wpdb->get_var( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT COUNT(p.ID)
+ FROM $wpdb->posts AS p
+ $nodata_join
+ LEFT JOIN $wpdb->postmeta AS mt1
+ ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
+ LEFT JOIN $wpdb->postmeta AS mt2
+ ON ( p.ID = mt2.post_id AND mt2.meta_key = '_imagify_data' )
+ WHERE
+ p.post_mime_type IN ( $mime_types )
+ AND ( mt1.meta_value = 'success' OR mt1.meta_value = 'already_optimized' )
+ AND mt2.meta_value NOT LIKE %s
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ $nodata_where",
+ '%' . $wpdb->esc_like( $webp_suffix . '";a:4:{s:7:"success";b:1;' ) . '%'
+ ) );
+ }
+
+ /**
+ * Get the context data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * The formated data.
+ *
+ * @type string $count-optimized Number of media optimized.
+ * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors.
+ * @type string $optimized-size Optimized filesize.
+ * @type string $original-size Original filesize.
+ * }
+ */
+ public function get_context_data() {
+ $total_saving_data = imagify_count_saving_data();
+ $data = [
+ 'count-optimized' => imagify_count_optimized_attachments(),
+ 'count-errors' => imagify_count_error_attachments(),
+ 'optimized-size' => $total_saving_data['optimized_size'],
+ 'original-size' => $total_saving_data['original_size'],
+ 'errors_url' => get_imagify_admin_url( 'folder-errors', $this->context ),
+ ];
+
+ return $this->format_context_data( $data );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/CDN/PushCDNInterface.php b/wp-content/plugins/imagify/classes/CDN/PushCDNInterface.php
new file mode 100644
index 00000000..763f91a9
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/CDN/PushCDNInterface.php
@@ -0,0 +1,96 @@
+context;
+ }
+
+ /**
+ * Tell if the context is network-wide.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_network_wide() {
+ return $this->is_network_wide;
+ }
+
+ /**
+ * Get the type of files this context allows.
+ *
+ * @since 1.9
+ * @access protected
+ * @see imagify_get_mime_types()
+ * @author Grégory Viguier
+ *
+ * @return string Possible values are:
+ * - 'all' to allow all types.
+ * - 'image' to allow only images.
+ * - 'not-image' to allow only pdf files.
+ */
+ public function get_allowed_mime_types() {
+ return $this->allowed_mime_types;
+ }
+
+ /**
+ * Get the thumbnail sizes for this context, except the full size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * Data for the currently registered thumbnail sizes.
+ * Size names are used as array keys.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * @type bool $crop True to crop, false to resize.
+ * @type string $name The size name.
+ * }
+ */
+ public function get_thumbnail_sizes() {
+ return $this->thumbnail_sizes;
+ }
+
+ /**
+ * Tell if the optimization process is allowed resize in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function can_resize() {
+ return $this->get_resizing_threshold() > 0;
+ }
+
+ /**
+ * Tell if the optimization process is allowed to backup in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function can_backup() {
+ return $this->can_backup;
+ }
+
+ /**
+ * Tell if the optimization process is allowed to keep exif in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function can_keep_exif() {
+ return $this->can_keep_exif;
+ }
+
+ /**
+ * Tell if the current user is allowed to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $describer Capacity describer. See $this->get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $media_id A media ID.
+ * @return bool
+ */
+ public function current_user_can( $describer, $media_id = null ) {
+ return $this->user_can( null, $describer, $media_id );
+ }
+
+ /**
+ * Tell if a user is allowed to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int|\WP_User $user_id A user ID or \WP_User object. Fallback to the current user ID.
+ * @param string $describer Capacity describer. See $this->get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $media_id A media ID.
+ * @return bool
+ */
+ public function user_can( $user_id, $describer, $media_id = null ) {
+ $current_user_id = get_current_user_id();
+
+ if ( ! $user_id ) {
+ $user = $current_user_id;
+ $user_id = $current_user_id;
+ } elseif ( $user_id instanceof \WP_User ) {
+ $user = $user_id;
+ $user_id = (int) $user->ID;
+ } elseif ( is_numeric( $user_id ) ) {
+ $user = (int) $user_id;
+ $user_id = $user;
+ } else {
+ $user_id = 0;
+ }
+
+ if ( ! $user_id ) {
+ return false;
+ }
+
+ $media_id = $media_id ? (int) $media_id : null;
+ $capacity = $this->get_capacity( $describer );
+
+ if ( $user_id === $current_user_id ) {
+ $user_can = current_user_can( $capacity, $media_id );
+
+ /**
+ * Tell if the current user is allowed to operate Imagify in this context.
+ *
+ * @since 1.6.11
+ * @since 1.9 Added the context name as parameter.
+ *
+ * @param bool $user_can Tell if the current user is allowed to operate Imagify in this context.
+ * @param string $capacity The user capacity.
+ * @param string $describer Capacity describer. See $this->get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $media_id A media ID.
+ * @param string $context The context name.
+ */
+ $user_can = (bool) apply_filters( 'imagify_current_user_can', $user_can, $capacity, $describer, $media_id, $this->get_name() );
+ } else {
+ $user_can = user_can( $user, $capacity, $media_id );
+ }
+
+ /**
+ * Tell if the given user is allowed to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $user_can Tell if the given user is allowed to operate Imagify in this context.
+ * @param int $user_id The user ID.
+ * @param string $capacity The user capacity.
+ * @param string $describer Capacity describer. See $this->get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $media_id A media ID.
+ * @param string $context The context name.
+ */
+ return (bool) apply_filters( 'imagify_user_can', $user_can, $user_id, $capacity, $describer, $media_id, $this->get_name() );
+ }
+
+ /**
+ * Filter a user capacity used to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $capacity The user capacity.
+ * @param string $describer Capacity describer. Possible values are like 'manage', 'bulk-optimize', 'manual-optimize', 'auto-optimize'.
+ * @return string
+ */
+ protected function filter_capacity( $capacity, $describer ) {
+ /**
+ * Filter a user capacity used to operate Imagify in this context.
+ *
+ * @since 1.0
+ * @since 1.6.5 Added $force_mono parameter.
+ * @since 1.6.11 Replaced $force_mono by $describer.
+ * @since 1.9 Added the context name as parameter.
+ *
+ * @param string $capacity The user capacity.
+ * @param string $describer Capacity describer. Possible values are like 'manage', 'bulk-optimize', 'manual-optimize', 'auto-optimize'.
+ * @param string $context The context name.
+ */
+ return (string) apply_filters( 'imagify_capacity', $capacity, $describer, $this->get_name() );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Context/ContextInterface.php b/wp-content/plugins/imagify/classes/Context/ContextInterface.php
new file mode 100644
index 00000000..ead8361c
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Context/ContextInterface.php
@@ -0,0 +1,165 @@
+get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $media_id A media ID.
+ * @return bool
+ */
+ public function current_user_can( $describer, $media_id = null );
+
+ /**
+ * Tell if a user is allowed to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $user_id A user ID.
+ * @param string $describer Capacity describer. See $this->get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $media_id A media ID.
+ * @return bool
+ */
+ public function user_can( $user_id, $describer, $media_id = null );
+
+ /**
+ * Get user capacity to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @since 1.9 The describer 'auto-optimize' is not used anymore.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $describer Capacity describer. Possible values are like 'manage', 'bulk-optimize', 'manual-optimize', 'auto-optimize'.
+ * @return string
+ */
+ public function get_capacity( $describer );
+}
diff --git a/wp-content/plugins/imagify/classes/Context/CustomFolders.php b/wp-content/plugins/imagify/classes/Context/CustomFolders.php
new file mode 100644
index 00000000..a8ebb404
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Context/CustomFolders.php
@@ -0,0 +1,126 @@
+can_backup ) ) {
+ return $this->can_backup;
+ }
+
+ $this->can_backup = get_imagify_option( 'backup' );
+
+ return $this->can_backup;
+ }
+
+ /**
+ * Tell if the optimization process is allowed to keep exif in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function can_keep_exif() {
+ if ( isset( $this->can_keep_exif ) ) {
+ return $this->can_keep_exif;
+ }
+
+ $this->can_keep_exif = get_imagify_option( 'exif' );
+
+ return $this->can_keep_exif;
+ }
+
+ /**
+ * Get user capacity to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $describer Capacity describer. Possible values are like 'manage', 'bulk-optimize', 'manual-optimize', 'auto-optimize'.
+ * @return string
+ */
+ public function get_capacity( $describer ) {
+ switch ( $describer ) {
+ case 'manage':
+ $capacity = imagify_is_active_for_network() ? 'manage_network_options' : 'manage_options';
+ break;
+
+ case 'bulk-optimize':
+ case 'optimize':
+ case 'restore':
+ case 'manual-optimize':
+ case 'manual-restore':
+ case 'auto-optimize':
+ $capacity = is_multisite() ? 'manage_network_options' : 'manage_options';
+ break;
+
+ default:
+ $capacity = $describer;
+ }
+
+ return $this->filter_capacity( $capacity, $describer );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Context/Noop.php b/wp-content/plugins/imagify/classes/Context/Noop.php
new file mode 100644
index 00000000..2cae5df6
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Context/Noop.php
@@ -0,0 +1,163 @@
+get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $media_id A media ID.
+ * @return bool
+ */
+ public function current_user_can( $describer, $media_id = null ) {
+ return false;
+ }
+
+ /**
+ * Tell if a user is allowed to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $user_id A user ID.
+ * @param string $describer Capacity describer. See $this->get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $media_id A media ID.
+ * @return bool
+ */
+ public function user_can( $user_id, $describer, $media_id = null ) {
+ return false;
+ }
+
+ /**
+ * Get user capacity to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $describer Capacity describer. Possible values are like 'manage', 'bulk-optimize', 'manual-optimize', 'auto-optimize'.
+ * @return string
+ */
+ public function get_capacity( $describer ) {
+ return 'noop';
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Context/WP.php b/wp-content/plugins/imagify/classes/Context/WP.php
new file mode 100644
index 00000000..bd10e3fd
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Context/WP.php
@@ -0,0 +1,173 @@
+thumbnail_sizes ) ) {
+ return $this->thumbnail_sizes;
+ }
+
+ $this->thumbnail_sizes = get_imagify_thumbnail_sizes();
+
+ return $this->thumbnail_sizes;
+ }
+
+ /**
+ * Get images max width for this context. This is used when resizing.
+ * 0 means to not resize.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int
+ */
+ public function get_resizing_threshold() {
+ if ( isset( $this->resizing_threshold ) ) {
+ return $this->resizing_threshold;
+ }
+
+ if ( ! get_imagify_option( 'resize_larger' ) ) {
+ $this->resizing_threshold = 0;
+ } else {
+ $this->resizing_threshold = max( 0, get_imagify_option( 'resize_larger_w' ) );
+ }
+
+ return $this->resizing_threshold;
+ }
+
+ /**
+ * Tell if the optimization process is allowed to backup in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function can_backup() {
+ if ( isset( $this->can_backup ) ) {
+ return $this->can_backup;
+ }
+
+ $this->can_backup = get_imagify_option( 'backup' );
+
+ return $this->can_backup;
+ }
+
+ /**
+ * Tell if the optimization process is allowed to keep exif in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function can_keep_exif() {
+ if ( isset( $this->can_keep_exif ) ) {
+ return $this->can_keep_exif;
+ }
+
+ $this->can_keep_exif = get_imagify_option( 'exif' );
+
+ return $this->can_keep_exif;
+ }
+
+ /**
+ * Get user capacity to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $describer Capacity describer. Possible values are like 'manage', 'bulk-optimize', 'manual-optimize', 'auto-optimize'.
+ * @return string
+ */
+ public function get_capacity( $describer ) {
+ static $edit_attachment_cap;
+
+ switch ( $describer ) {
+ case 'manage':
+ $capacity = imagify_is_active_for_network() ? 'manage_network_options' : 'manage_options';
+ break;
+
+ case 'bulk-optimize':
+ $capacity = 'manage_options';
+ break;
+
+ case 'optimize':
+ case 'restore':
+ // This is a generic capacity: don't use it unless you have no other choices!
+ if ( ! isset( $edit_attachment_cap ) ) {
+ $edit_attachment_cap = get_post_type_object( 'attachment' );
+ $edit_attachment_cap = $edit_attachment_cap ? $edit_attachment_cap->cap->edit_posts : 'edit_posts';
+ }
+
+ $capacity = $edit_attachment_cap;
+ break;
+
+ case 'manual-optimize':
+ case 'manual-restore':
+ // Must be used with an Attachment ID.
+ $capacity = 'edit_post';
+ break;
+
+ case 'auto-optimize':
+ $capacity = 'upload_files';
+ break;
+
+ default:
+ $capacity = $describer;
+ }
+
+ return $this->filter_capacity( $capacity, $describer );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/DB/DBInterface.php b/wp-content/plugins/imagify/classes/DB/DBInterface.php
new file mode 100644
index 00000000..6cde75c7
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/DB/DBInterface.php
@@ -0,0 +1,84 @@
+ 'imagify_requirements',
+ 'bulk_optimization_stats' => 'imagify_bulk_optimization_stats',
+ 'bulk_optimization_status' => 'imagify_bulk_optimization_status',
+ 'options_optimization_status' => 'imagify_options_optimization_status',
+ 'library_optimization_status' => 'imagify_library_optimization_status',
+ 'custom_folders_optimization_status' => 'imagify_custom_folders_optimization_status',
+ ];
+
+ /**
+ * Class init: launch hooks.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function init() {
+ foreach ( $this->imagifybeat_ids as $action => $imagifybeat_id ) {
+ add_filter( 'imagifybeat_received', [ $this, 'add_' . $action . '_to_response' ], 10, 2 );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** IMAGIFYBEAT CALLBACKS =================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Add requirements to Imagifybeat data.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ public function add_requirements_to_response( $response, $data ) {
+ $imagifybeat_id = $this->get_imagifybeat_id_for_callback( __FUNCTION__ );
+
+ if ( ! $imagifybeat_id || empty( $data[ $imagifybeat_id ] ) ) {
+ return $response;
+ }
+
+ $response[ $imagifybeat_id ] = [
+ 'curl_missing' => ! Imagify_Requirements::supports_curl(),
+ 'editor_missing' => ! Imagify_Requirements::supports_image_editor(),
+ 'external_http_blocked' => Imagify_Requirements::is_imagify_blocked(),
+ 'api_down' => ! Imagify_Requirements::is_api_up(),
+ 'key_is_valid' => Imagify_Requirements::is_api_key_valid(),
+ 'is_over_quota' => Imagify_Requirements::is_over_quota(),
+ ];
+
+ return $response;
+ }
+
+ /**
+ * Add bulk stats to Imagifybeat data.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ public function add_bulk_optimization_stats_to_response( $response, $data ) {
+ $imagifybeat_id = $this->get_imagifybeat_id_for_callback( __FUNCTION__ );
+
+ if ( ! $imagifybeat_id || empty( $data[ $imagifybeat_id ] ) ) {
+ return $response;
+ }
+
+ $folder_types = array_flip( array_filter( $data[ $imagifybeat_id ] ) );
+
+ $response[ $imagifybeat_id ] = imagify_get_bulk_stats(
+ $folder_types,
+ [
+ 'fullset' => true,
+ ]
+ );
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ * This is used in the bulk optimization page.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ public function add_bulk_optimization_status_to_response( $response, $data ) {
+ $imagifybeat_id = $this->get_imagifybeat_id_for_callback( __FUNCTION__ );
+
+ if ( ! $imagifybeat_id || empty( $data[ $imagifybeat_id ] ) || ! is_array( $data[ $imagifybeat_id ] ) ) {
+ return $response;
+ }
+
+ $statuses = [];
+
+ foreach ( $data[ $imagifybeat_id ] as $item ) {
+ if ( empty( $statuses[ $item['context'] ] ) ) {
+ $statuses[ $item['context'] ] = [];
+ }
+
+ $statuses[ $item['context'] ][ '_' . $item['mediaID'] ] = 1;
+ }
+
+ $results = $this->get_modified_optimization_statuses( $statuses );
+
+ if ( ! $results ) {
+ return $response;
+ }
+
+ $response[ $imagifybeat_id ] = [];
+
+ // Sanitize received data and grab some other info.
+ foreach ( $results as $context_id => $media_atts ) {
+ $process = imagify_get_optimization_process( $media_atts['media_id'], $media_atts['context'] );
+ $optim_data = $process->get_data();
+
+ if ( $optim_data->is_optimized() ) {
+ // Successfully optimized.
+ $full_size_data = $optim_data->get_size_data();
+ $full_size_data = array_merge(
+ [
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ ],
+ $full_size_data
+ );
+ $response[ $imagifybeat_id ][] = [
+ 'mediaID' => $media_atts['media_id'],
+ 'context' => $media_atts['context'],
+ 'success' => true,
+ 'status' => 'optimized',
+ // Raw data.
+ 'originalOverallSize' => $full_size_data['original_size'],
+ 'newOverallSize' => $full_size_data['optimized_size'],
+ 'overallSaving' => $full_size_data['original_size'] - $full_size_data['optimized_size'],
+ 'thumbnailsCount' => $optim_data->get_optimized_sizes_count(),
+ // Human readable data.
+ 'originalSizeHuman' => imagify_size_format( $full_size_data['original_size'], 2 ),
+ 'newSizeHuman' => imagify_size_format( $full_size_data['optimized_size'], 2 ),
+ 'overallSavingHuman' => imagify_size_format( $full_size_data['original_size'] - $full_size_data['optimized_size'], 2 ),
+ 'originalOverallSizeHuman' => imagify_size_format( $full_size_data['original_size'], 2 ),
+ 'percentHuman' => $full_size_data['percent'] . '%',
+ ];
+ } elseif ( $optim_data->is_already_optimized() ) {
+ // Already optimized.
+ $response[ $imagifybeat_id ][] = [
+ 'mediaID' => $media_atts['media_id'],
+ 'context' => $media_atts['context'],
+ 'success' => true,
+ 'status' => 'already-optimized',
+ ];
+ } else {
+ // Error.
+ $full_size_data = $optim_data->get_size_data();
+ $message = ! empty( $full_size_data['error'] ) ? $full_size_data['error'] : '';
+ $status = 'error';
+
+ if ( 'You\'ve consumed all your data. You have to upgrade your account to continue' === $message ) {
+ $status = 'over-quota';
+ }
+
+ $response[ $imagifybeat_id ][] = [
+ 'mediaID' => $media_atts['media_id'],
+ 'context' => $media_atts['context'],
+ 'success' => false,
+ 'status' => $status,
+ 'error' => imagify_translate_api_message( $message ),
+ ];
+ }
+ }
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ * This is used in the settings page.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ public function add_options_optimization_status_to_response( $response, $data ) {
+ $imagifybeat_id = $this->get_imagifybeat_id_for_callback( __FUNCTION__ );
+
+ if ( ! $imagifybeat_id || empty( $data[ $imagifybeat_id ] ) || ! is_array( $data[ $imagifybeat_id ] ) ) {
+ return $response;
+ }
+
+ $statuses = [];
+
+ foreach ( $data[ $imagifybeat_id ] as $item ) {
+ if ( empty( $statuses[ $item['context'] ] ) ) {
+ $statuses[ $item['context'] ] = [];
+ }
+
+ $statuses[ $item['context'] ][ '_' . $item['mediaID'] ] = 1;
+ }
+
+ $results = $this->get_modified_optimization_statuses( $statuses );
+
+ if ( ! $results ) {
+ return $response;
+ }
+
+ $response[ $imagifybeat_id ] = [];
+
+ foreach ( $results as $result ) {
+ $response[ $imagifybeat_id ][] = [
+ 'mediaID' => $result['media_id'],
+ 'context' => $result['context'],
+ ];
+ }
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ * This is used in the WP Media Library.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ public function add_library_optimization_status_to_response( $response, $data ) {
+ $imagifybeat_id = $this->get_imagifybeat_id_for_callback( __FUNCTION__ );
+
+ if ( ! $imagifybeat_id || empty( $data[ $imagifybeat_id ] ) || ! is_array( $data[ $imagifybeat_id ] ) ) {
+ return $response;
+ }
+
+ $response[ $imagifybeat_id ] = $this->get_modified_optimization_statuses( $data[ $imagifybeat_id ] );
+
+ if ( ! $response[ $imagifybeat_id ] ) {
+ return $response;
+ }
+
+ // Sanitize received data and grab some other info.
+ foreach ( $response[ $imagifybeat_id ] as $context_id => $media_atts ) {
+ $process = imagify_get_optimization_process( $media_atts['media_id'], $media_atts['context'] );
+
+ $response[ $imagifybeat_id ][ $context_id ] = get_imagify_media_column_content( $process, false );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ * This is used in the custom folders list (the "Other Media" page).
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ public function add_custom_folders_optimization_status_to_response( $response, $data ) {
+ $imagifybeat_id = $this->get_imagifybeat_id_for_callback( __FUNCTION__ );
+
+ if ( ! $imagifybeat_id || empty( $data[ $imagifybeat_id ] ) || ! is_array( $data[ $imagifybeat_id ] ) ) {
+ return $response;
+ }
+
+ $response[ $imagifybeat_id ] = $this->get_modified_optimization_statuses( $data[ $imagifybeat_id ] );
+
+ if ( ! $response[ $imagifybeat_id ] ) {
+ return $response;
+ }
+
+ $admin_ajax_post = \Imagify_Admin_Ajax_Post::get_instance();
+ $list_table = new \Imagify_Files_List_Table( [
+ 'screen' => 'imagify-files',
+ ] );
+
+ // Sanitize received data and grab some other info.
+ foreach ( $response[ $imagifybeat_id ] as $context_id => $media_atts ) {
+ $process = imagify_get_optimization_process( $media_atts['media_id'], $media_atts['context'] );
+
+ $response[ $imagifybeat_id ][ $context_id ] = $admin_ajax_post->get_media_columns( $process, $list_table );
+ }
+
+ return $response;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $data The data received.
+ * @return array
+ */
+ public function get_modified_optimization_statuses( $data ) {
+ if ( ! $data ) {
+ return [];
+ }
+
+ $output = [];
+
+ // Sanitize received data and grab some other info.
+ foreach ( $data as $context => $media_statuses ) {
+ if ( ! $context || ! $media_statuses || ! is_array( $media_statuses ) ) {
+ continue;
+ }
+
+ // Sanitize the IDs: IDs come as strings, prefixed with an undescore character (to prevent JavaScript from screwing everything).
+ $media_ids = array_keys( $media_statuses );
+ $media_ids = array_map( function( $media_id ) {
+ return (int) substr( $media_id, 1 );
+ }, $media_ids );
+ $media_ids = array_filter( $media_ids );
+
+ if ( ! $media_ids ) {
+ continue;
+ }
+
+ // Sanitize the context.
+ $context_instance = imagify_get_context( $context );
+ $context = $context_instance->get_name();
+ $process_class_name = imagify_get_optimization_process_class_name( $context );
+ $transient_name = sprintf( $process_class_name::LOCK_NAME, $context, '%' );
+ $is_network_wide = $context_instance->is_network_wide();
+
+ \Imagify_DB::cache_process_locks( $context, $media_ids );
+
+ // Now that everything is cached for this context, we can get the transients without hitting the DB.
+ foreach ( $media_ids as $id ) {
+ $is_locked = (bool) $media_statuses[ '_' . $id ];
+ $option_name = str_replace( '%', $id, $transient_name );
+
+ if ( $is_network_wide ) {
+ $in_db = (bool) get_site_transient( $option_name );
+ } else {
+ $in_db = (bool) get_transient( $option_name );
+ }
+
+ if ( $is_locked === $in_db ) {
+ continue;
+ }
+
+ $output[ $context . '_' . $id ] = [
+ 'media_id' => $id,
+ 'context' => $context,
+ ];
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Get an Imagifybeat ID, given an action.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $action An action corresponding to the ID we want.
+ * @return string|bool The ID. False on failure.
+ */
+ public function get_imagifybeat_id( $action ) {
+ if ( ! empty( $this->imagifybeat_ids[ $action ] ) ) {
+ return $this->imagifybeat_ids[ $action ];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get an Imagifybeat ID, given a callback name.
+ *
+ * @since 1.9.3
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param string $callback A methodâs name.
+ * @return string|bool The ID. False on failure.
+ */
+ private function get_imagifybeat_id_for_callback( $callback ) {
+ if ( preg_match( '@^add_(?.+)_to_response$@', $callback, $matches ) ) {
+ return $this->get_imagifybeat_id( $matches['id'] );
+ }
+
+ return false;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Imagifybeat/Core.php b/wp-content/plugins/imagify/classes/Imagifybeat/Core.php
new file mode 100644
index 00000000..15c4300a
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Imagifybeat/Core.php
@@ -0,0 +1,172 @@
+id and the JS global 'pagenow'.
+ if ( ! empty( $_POST['screen_id'] ) ) {
+ $screen_id = sanitize_key( $_POST['screen_id'] );
+ } else {
+ $screen_id = 'front';
+ }
+
+ if ( ! empty( $_POST['data'] ) ) {
+ $data = wp_unslash( (array) $_POST['data'] );
+ }
+
+ if ( 1 !== $nonce_state ) {
+ /**
+ * Filters the nonces to send.
+ *
+ * @since 1.9.3
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @param string $screen_id The screen id.
+ */
+ $response = apply_filters( 'imagifybeat_refresh_nonces', $response, $data, $screen_id );
+
+ if ( false === $nonce_state ) {
+ // User is logged in but nonces have expired.
+ $response['nonces_expired'] = true;
+ wp_send_json( $response );
+ }
+ }
+
+ if ( ! empty( $data ) ) {
+ /**
+ * Filters the Imagifybeat response received.
+ *
+ * @since 1.9.3
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @param string $screen_id The screen id.
+ */
+ $response = apply_filters( 'imagifybeat_received', $response, $data, $screen_id );
+ }
+
+ /**
+ * Filters the Imagifybeat response sent.
+ *
+ * @since 1.9.3
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param string $screen_id The screen id.
+ */
+ $response = apply_filters( 'imagifybeat_send', $response, $screen_id );
+
+ /**
+ * Fires when Imagifybeat ticks in logged-in environments.
+ *
+ * Allows the transport to be easily replaced with long-polling.
+ *
+ * @since 1.9.3
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @param string $screen_id The screen id.
+ */
+ do_action( 'imagifybeat_tick', $response, $screen_id );
+
+ // Send the current time according to the server.
+ $response['server_time'] = time();
+
+ wp_send_json( $response );
+ }
+
+ /**
+ * Add the latest Imagifybeat nonce to the Imagifybeat response.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $response The Imagifybeat response.
+ * @return array The Imagifybeat response.
+ */
+ public function refresh_imagifybeat_nonces( $response ) {
+ // Refresh the Imagifybeat nonce.
+ $response['imagifybeat_nonce'] = wp_create_nonce( 'imagifybeat-nonce' );
+ return $response;
+ }
+
+ /**
+ * Get Imagifybeat settings.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_settings() {
+ global $pagenow;
+
+ $settings = [];
+
+ if ( ! is_admin() ) {
+ $settings['ajaxurl'] = admin_url( 'admin-ajax.php', 'relative' );
+ }
+
+ if ( is_user_logged_in() ) {
+ $settings['nonce'] = wp_create_nonce( 'imagifybeat-nonce' );
+ }
+
+ if ( 'customize.php' === $pagenow ) {
+ $settings['screenId'] = 'customize';
+ }
+
+ /**
+ * Filters the Imagifybeat settings.
+ *
+ * @since 1.9.3
+ * @author Grégory Viguier
+ *
+ * @param array $settings Imagifybeat settings array.
+ */
+ return (array) apply_filters( 'imagifybeat_settings', $settings );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Job/MediaOptimization.php b/wp-content/plugins/imagify/classes/Job/MediaOptimization.php
new file mode 100644
index 00000000..3de995dc
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Job/MediaOptimization.php
@@ -0,0 +1,403 @@
+validate_item( $item );
+
+ if ( ! $item ) {
+ // Not valid.
+ return false;
+ }
+
+ // Launch the task.
+ $method = 'task_' . $item['task'];
+ $item = $this->$method( $item );
+
+ if ( $item['task'] ) {
+ // Next task.
+ return $item;
+ }
+
+ // End of the queue.
+ $this->optimization_process->unlock();
+ return false;
+ }
+
+ /**
+ * Trigger hooks before the optimization job.
+ *
+ * @since 1.9
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param array $item See $this->task().
+ * @return array The item.
+ */
+ private function task_before( $item ) {
+ if ( ! empty( $item['error'] ) && is_wp_error( $item['error'] ) ) {
+ $wp_error = $item['error'];
+ } else {
+ $wp_error = new \WP_Error();
+ }
+
+ /**
+ * Fires before optimizing a media.
+ * Any number of files can be optimized, not necessarily all of the media files.
+ * If you want to return a WP_Error, use the existing $wp_error object.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array|\WP_Error $data New data to pass along the item. A \WP_Error object to stop the process.
+ * @param \WP_Error $wp_error Add errors to this object and return it to stop the process.
+ * @param ProcessInterface $process The optimization process.
+ * @param array $item The item being processed. See $this->task().
+ */
+ $data = apply_filters( 'imagify_before_optimize', [], $wp_error, $this->optimization_process, $item );
+
+ if ( is_wp_error( $data ) ) {
+ $wp_error = $data;
+ } elseif ( $data && is_array( $data ) ) {
+ $item['data'] = array_merge( $data, $item['data'] );
+ }
+
+ if ( $wp_error->get_error_codes() ) {
+ // Don't optimize if there is an error.
+ $item['task'] = 'after';
+ $item['error'] = $wp_error;
+ return $item;
+ }
+
+ if ( empty( $item['data']['hook_suffix'] ) ) {
+ // Next task.
+ $item['task'] = 'optimize';
+ return $item;
+ }
+
+ $hook_suffix = $item['data']['hook_suffix'];
+
+ /**
+ * Fires before optimizing a media.
+ * Any number of files can be optimized, not necessarily all of the media files.
+ * If you want to return a WP_Error, use the existing $wp_error object.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array|\WP_Error $data New data to pass along the item. A \WP_Error object to stop the process.
+ * @param \WP_Error $wp_error Add errors to this object and return it to stop the process.
+ * @param ProcessInterface $process The optimization process.
+ * @param array $item The item being processed. See $this->task().
+ */
+ $data = apply_filters( "imagify_before_{$hook_suffix}", [], $wp_error, $this->optimization_process, $item );
+
+ if ( is_wp_error( $data ) ) {
+ $wp_error = $data;
+ } elseif ( $data && is_array( $data ) ) {
+ $item['data'] = array_merge( $data, $item['data'] );
+ }
+
+ if ( $wp_error->get_error_codes() ) {
+ // Don't optimize if there is an error.
+ $item['task'] = 'after';
+ $item['error'] = $wp_error;
+ return $item;
+ }
+
+ // Next task.
+ $item['task'] = 'optimize';
+
+ return $item;
+ }
+
+ /**
+ * Start the optimization job.
+ *
+ * @since 1.9
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param array $item See $this->task().
+ * @return array The item.
+ */
+ private function task_optimize( $item ) {
+ // Determine which size we're going to optimize. The 'full' size must be optimized before any other.
+ if ( in_array( 'full', $item['sizes'], true ) ) {
+ $current_size = 'full';
+ $item['sizes'] = array_diff( $item['sizes'], [ 'full' ] );
+ } else {
+ $current_size = array_shift( $item['sizes'] );
+ }
+
+ $item['sizes_done'][] = $current_size;
+
+ // Optimize the file.
+ $data = $this->optimization_process->optimize_size( $current_size, $item['optimization_level'] );
+
+ if ( 'full' === $current_size ) {
+ if ( is_wp_error( $data ) ) {
+ // Don't go further if there is an error.
+ $item['sizes'] = [];
+ $item['error'] = $data;
+
+ } elseif ( 'already_optimized' === $data['status'] ) {
+ // Status is "already_optimized", try to create webp versions only.
+ $item['sizes'] = array_filter( $item['sizes'], [ $this->optimization_process, 'is_size_webp' ] );
+
+ } elseif ( 'success' !== $data['status'] ) {
+ // Don't go further if the full size has not the "success" status.
+ $item['sizes'] = [];
+ }
+ }
+
+ if ( ! $item['sizes'] ) {
+ // No more files to optimize.
+ $item['task'] = 'after';
+ }
+
+ // Optimize the next file or go to the next task.
+ return $item;
+ }
+
+ /**
+ * Trigger hooks after the optimization job.
+ *
+ * @since 1.9
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param array $item See $this->task().
+ * @return array The item.
+ */
+ private function task_after( $item ) {
+ if ( ! empty( $item['data']['delete_backup'] ) ) {
+ $this->optimization_process->delete_backup();
+ }
+
+ /**
+ * Fires after optimizing a media.
+ * Any number of files can be optimized, not necessarily all of the media files.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process.
+ * @param array $item The item being processed. See $this->task().
+ */
+ do_action( 'imagify_after_optimize', $this->optimization_process, $item );
+
+ if ( empty( $item['data']['hook_suffix'] ) ) {
+ $item['task'] = false;
+ return $item;
+ }
+
+ $hook_suffix = $item['data']['hook_suffix'];
+
+ /**
+ * Fires after optimizing a media.
+ * Any number of files can be optimized, not necessarily all of the media files.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process.
+ * @param array $item The item being processed. See $this->task().
+ */
+ do_action( "imagify_after_{$hook_suffix}", $this->optimization_process, $item );
+
+ $item['task'] = false;
+ return $item;
+ }
+
+ /**
+ * Validate an item.
+ * On success, the property $this->optimization_process is set.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $item See $this->task().
+ * @return array|bool The item. False if invalid.
+ */
+ protected function validate_item( $item ) {
+ $this->optimization_process = null;
+
+ $default = [
+ 'task' => '',
+ 'id' => 0,
+ 'sizes' => [],
+ 'sizes_done' => [],
+ 'optimization_level' => null,
+ 'process_class' => '',
+ 'data' => [],
+ ];
+
+ $item = imagify_merge_intersect( $item, $default );
+
+ // Validate some types first.
+ if ( ! is_array( $item['sizes'] ) ) {
+ return false;
+ }
+
+ if ( isset( $item['error'] ) && ! is_wp_error( $item['error'] ) ) {
+ unset( $item['error'] );
+ }
+
+ if ( isset( $item['data']['hook_suffix'] ) && ! is_string( $item['data']['hook_suffix'] ) ) {
+ unset( $item['data']['hook_suffix'] );
+ }
+
+ $item['id'] = (int) $item['id'];
+ $item['optimization_level'] = $this->sanitize_optimization_level( $item['optimization_level'] );
+
+ if ( ! $item['id'] || ! $item['process_class'] ) {
+ return false;
+ }
+
+ // Process.
+ $item['process_class'] = '\\' . ltrim( $item['process_class'], '\\' );
+
+ if ( ! class_exists( $item['process_class'] ) ) {
+ return false;
+ }
+
+ $process = $this->get_process( $item );
+
+ if ( ! $process ) {
+ return false;
+ }
+
+ $this->optimization_process = $process;
+
+ // Validate the current task.
+ if ( empty( $item['task'] ) ) {
+ $item['task'] = 'before';
+ }
+
+ if ( ! $item['task'] || ! method_exists( $this, 'task_' . $item['task'] ) ) {
+ return false;
+ }
+
+ if ( ! $item['sizes'] && 'after' !== $item['task'] ) {
+ // Allow to have no sizes, but only after the optimize task is complete.
+ return false;
+ }
+
+ if ( ! isset( $item['sizes_done'] ) || ! is_array( $item['sizes_done'] ) ) {
+ $item['sizes_done'] = [];
+ }
+
+ return $item;
+ }
+
+ /**
+ * Get the process instance.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $item See $this->task().
+ * @return ProcessInterface|bool The instance object on success. False on failure.
+ */
+ protected function get_process( $item ) {
+ $process_class = $item['process_class'];
+ $process = new $process_class( $item['id'] );
+
+ if ( ! $process instanceof ProcessInterface || ! $process->is_valid() ) {
+ return false;
+ }
+
+ return $process;
+ }
+
+ /**
+ * Sanitize and validate an optimization level.
+ * If not provided (false, null), fallback to the level set in the plugin's settings.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param mixed $optimization_level The optimization level.
+ * @return int
+ */
+ protected function sanitize_optimization_level( $optimization_level ) {
+ if ( ! is_numeric( $optimization_level ) ) {
+ return get_imagify_option( 'optimization_level' );
+ }
+
+ return \Imagify_Options::get_instance()->sanitize_and_validate( 'optimization_level', $optimization_level );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Media/AbstractMedia.php b/wp-content/plugins/imagify/classes/Media/AbstractMedia.php
new file mode 100644
index 00000000..87647597
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Media/AbstractMedia.php
@@ -0,0 +1,552 @@
+is_image()
+ * @author Grégory Viguier
+ */
+ protected $is_image;
+
+ /**
+ * Tell if the file is a pdf.
+ *
+ * @var bool
+ * @since 1.9
+ * @access protected
+ * @see $this->is_pdf()
+ * @author Grégory Viguier
+ */
+ protected $is_pdf;
+
+ /**
+ * Store the file mime type + file extension (if the file is supported).
+ *
+ * @var array
+ * @since 1.9
+ * @access protected
+ * @see $this->get_file_type()
+ * @author Grégory Viguier
+ */
+ protected $file_type;
+
+ /**
+ * Filesystem object.
+ *
+ * @var object Imagify_Filesystem
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $filesystem;
+
+ /**
+ * The constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $id The media ID.
+ */
+ public function __construct( $id ) {
+ $this->id = (int) $id;
+ $this->filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Get the media ID.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int
+ */
+ public function get_id() {
+ return $this->id;
+ }
+
+ /**
+ * Tell if the current media is valid.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_valid() {
+ return $this->get_id() > 0;
+ }
+
+ /**
+ * Get the media context name.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_context() {
+ return $this->get_context_instance()->get_name();
+ }
+
+ /**
+ * Get the media context instance.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return ContextInterface
+ */
+ public function get_context_instance() {
+ if ( $this->context ) {
+ if ( is_string( $this->context ) ) {
+ $this->context = imagify_get_context( $this->context );
+ }
+
+ return $this->context;
+ }
+
+ $class_name = get_class( $this );
+ $class_name = '\\' . trim( $class_name, '\\' );
+ $class_name = str_replace( '\\Media\\', '\\Context\\', $class_name );
+ $this->context = new $class_name();
+
+ return $this->context;
+ }
+
+ /**
+ * Get the CDN instance.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|PushCDNInterface A PushCDNInterface instance. False if no CDN is used.
+ */
+ public function get_cdn() {
+ if ( isset( $this->cdn ) ) {
+ return $this->cdn;
+ }
+
+ if ( ! $this->is_valid() ) {
+ $this->cdn = false;
+ return $this->cdn;
+ }
+
+ $media_id = $this->get_id();
+ $context = $this->get_context_instance();
+
+ /**
+ * The CDN to use for this media.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool|PushCDNInterface $cdn A PushCDNInterface instance. False if no CDN is used.
+ * @param int $media_id The media ID.
+ * @param ContextInterface $context The context object.
+ */
+ $this->cdn = apply_filters( 'imagify_cdn', false, $media_id, $context );
+
+ if ( ! $this->cdn || ! $this->cdn instanceof PushCDNInterface ) {
+ $this->cdn = false;
+ return $this->cdn;
+ }
+
+ return $this->cdn;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** ORIGINAL FILE =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the original media's path if the file exists.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False if it doesn't exist.
+ */
+ public function get_original_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ $original_path = $this->get_raw_original_path();
+
+ if ( ! $original_path || ! $this->filesystem->exists( $original_path ) ) {
+ return false;
+ }
+
+ return $original_path;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** FULL SIZE FILE ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the path to the mediaâs full size file if the file exists.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False if it doesn't exist.
+ */
+ public function get_fullsize_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ $original_path = $this->get_raw_fullsize_path();
+
+ if ( ! $original_path || ! $this->filesystem->exists( $original_path ) ) {
+ return false;
+ }
+
+ return $original_path;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BACKUP FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the backup file path if the file exists.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False if it doesn't exist.
+ */
+ public function get_backup_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ $backup_path = $this->get_raw_backup_path();
+
+ if ( ! $backup_path || ! $this->filesystem->exists( $backup_path ) ) {
+ return false;
+ }
+
+ return $backup_path;
+ }
+
+ /**
+ * Check if the media has a backup of the original file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool True if the media has a backup.
+ */
+ public function has_backup() {
+ return (bool) $this->get_backup_path();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** MEDIA DATA ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if the current media type is supported.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_supported() {
+ return (bool) $this->get_mime_type();
+ }
+
+ /**
+ * Tell if the current media refers to an image, based on file extension.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool Returns false in case it's an image but not in a supported format (bmp for example).
+ */
+ public function is_image() {
+ if ( isset( $this->is_image ) ) {
+ return $this->is_image;
+ }
+
+ $this->is_image = strpos( (string) $this->get_mime_type(), 'image/' ) === 0;
+
+ return $this->is_image;
+ }
+
+ /**
+ * Tell if the current media refers to a pdf, based on file extension.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_pdf() {
+ if ( isset( $this->is_pdf ) ) {
+ return $this->is_pdf;
+ }
+
+ $this->is_pdf = 'application/pdf' === $this->get_mime_type();
+
+ return $this->is_pdf;
+ }
+
+ /**
+ * Get the original file extension (if supported by Imagify).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|null
+ */
+ public function get_extension() {
+ return $this->get_file_type()->ext;
+ }
+
+ /**
+ * Get the original file mime type (if supported by Imagify).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_mime_type() {
+ return $this->get_file_type()->type;
+ }
+
+ /**
+ * Get the file mime type + file extension (if the file is supported by Imagify).
+ * This test is ran against the original file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_allowed_mime_types() {
+ return imagify_get_mime_types( $this->get_context_instance()->get_allowed_mime_types() );
+ }
+
+ /**
+ * If the media is an image, update the dimensions in the database with the current file dimensions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool True on success. False on failure.
+ */
+ public function update_dimensions() {
+ if ( ! $this->is_image() ) {
+ // The media is not a supported image.
+ return false;
+ }
+
+ $dimensions = $this->filesystem->get_image_size( $this->get_raw_fullsize_path() );
+
+ if ( ! $dimensions ) {
+ // Could not get the new dimensions.
+ return false;
+ }
+
+ $context = $this->get_context();
+
+ /**
+ * Triggered before updating an image width and height into its metadata.
+ *
+ * @since 1.9
+ * @see Imagify_Filesystem->get_image_size()
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param array $dimensions {
+ * An array with, among other data:
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ */
+ do_action( "imagify_before_update_{$context}_media_data_dimensions", $this->get_id(), $dimensions );
+
+ $this->update_media_data_dimensions( $dimensions );
+
+ /**
+ * Triggered after updating an image width and height into its metadata.
+ *
+ * @since 1.9
+ * @see Imagify_Filesystem->get_image_size()
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param array $dimensions {
+ * An array with, among other data:
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ */
+ do_action( "imagify_after_update_{$context}_media_data_dimensions", $this->get_id(), $dimensions );
+
+ return true;
+ }
+
+ /**
+ * Update the media data dimensions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $dimensions {
+ * An array containing width and height.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ */
+ abstract protected function update_media_data_dimensions( $dimensions );
+
+ /**
+ * Get the file mime type + file extension (if the file is supported by Imagify).
+ * This test is ran against the original file.
+ *
+ * @since 1.9
+ * @access protected
+ * @see wp_check_filetype()
+ * @author Grégory Viguier
+ *
+ * @return object
+ */
+ protected function get_file_type() {
+ if ( isset( $this->file_type ) ) {
+ return $this->file_type;
+ }
+
+ $this->file_type = (object) [
+ 'ext' => '',
+ 'type' => '',
+ ];
+
+ if ( ! $this->is_valid() ) {
+ return $this->file_type;
+ }
+
+ $path = $this->get_raw_fullsize_path();
+
+ if ( ! $path ) {
+ return $this->file_type;
+ }
+
+ $this->file_type = (object) wp_check_filetype( $path, $this->get_allowed_mime_types() );
+
+ return $this->file_type;
+ }
+
+ /**
+ * Filter the result of $this->get_media_files().
+ *
+ * @since 1.9
+ * @access protected
+ * @see $this->get_media_files()
+ * @author Grégory Viguier
+ *
+ * @param array $files An array with the size names as keys ('full' is used for the full size file), and arrays of data as values.
+ * @return array
+ */
+ protected function filter_media_files( $files ) {
+ /**
+ * Filter the media files.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $files An array with the size names as keys ('full' is used for the full size file), and arrays of data as values.
+ * @param MediaInterface $media This instance.
+ */
+ return (array) apply_filters( 'imagify_media_files', $files, $this );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Media/CustomFolders.php b/wp-content/plugins/imagify/classes/Media/CustomFolders.php
new file mode 100644
index 00000000..72456cdb
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Media/CustomFolders.php
@@ -0,0 +1,358 @@
+invalidate_row();
+ parent::__construct( 0 );
+ return;
+ }
+
+ if ( is_numeric( $id ) ) {
+ $this->id = (int) $id;
+ $this->get_row();
+ } else {
+ $prim_key = $this->get_row_db_instance()->get_primary_key();
+ $this->row = (array) $id;
+ $this->id = $this->row[ $prim_key ];
+ }
+
+ parent::__construct( $this->id );
+ }
+
+ /**
+ * Tell if the given entry can be accepted in the constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param mixed $id Whatever.
+ * @return bool
+ */
+ public static function constructor_accepts( $id ) {
+ return $id && ( is_numeric( $id ) || is_array( $id ) || is_object( $id ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** ORIGINAL FILE =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the original media's path.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_original_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_path( 'original' );
+ }
+
+ $row = $this->get_row();
+
+ if ( ! $row || empty( $row['path'] ) ) {
+ return false;
+ }
+
+ return \Imagify_Files_Scan::remove_placeholder( $row['path'] );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** FULL SIZE FILE ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the URL of the mediaâs full size file.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file URL. False on failure.
+ */
+ public function get_fullsize_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_url();
+ }
+
+ $row = $this->get_row();
+
+ if ( ! $row || empty( $row['path'] ) ) {
+ return false;
+ }
+
+ return \Imagify_Files_Scan::remove_placeholder( $row['path'], 'url' );
+ }
+
+ /**
+ * Get the path to the mediaâs full size file, even if the file doesn't exist.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_fullsize_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_path();
+ }
+
+ $row = $this->get_row();
+
+ if ( ! $row || empty( $row['path'] ) ) {
+ return false;
+ }
+
+ return \Imagify_Files_Scan::remove_placeholder( $row['path'] );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BACKUP FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the backup URL, even if the file doesn't exist.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file URL. False on failure.
+ */
+ public function get_backup_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return site_url( $this->filesystem->make_path_relative( $this->get_raw_backup_path() ) );
+ }
+
+ /**
+ * Get the backup file path, even if the file doesn't exist.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_backup_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return \Imagify_Custom_Folders::get_file_backup_path( $this->get_raw_original_path() );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** THUMBNAILS ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Create the media thumbnails.
+ * And since this context does not support thumbnails...
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True on success. A \WP_Error instance on failure.
+ */
+ public function generate_thumbnails() {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ return true;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** MEDIA DATA ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if the current media has the required data (the data containing the file paths and thumbnails).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_required_media_data() {
+ return $this->is_valid();
+ }
+
+ /**
+ * Get the list of the files of this media, including the full size file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * An array with the size names as keys ('full' is used for the full size file), and arrays of data as values:
+ *
+ * @type string $size The size name.
+ * @type string $path Absolute path to the file.
+ * @type int $width The file width.
+ * @type int $height The file height.
+ * @type string $mime-type The file mime type.
+ * @type bool $disabled True if the size is disabled in the pluginâs settings.
+ * }
+ */
+ public function get_media_files() {
+ if ( ! $this->is_valid() ) {
+ return [];
+ }
+
+ $fullsize_path = $this->get_raw_fullsize_path();
+
+ if ( ! $fullsize_path ) {
+ return [];
+ }
+
+ $dimensions = $this->get_dimensions();
+ $sizes = [
+ 'full' => [
+ 'size' => 'full',
+ 'path' => $fullsize_path,
+ 'width' => $dimensions['width'],
+ 'height' => $dimensions['height'],
+ 'mime-type' => $this->get_mime_type(),
+ 'disabled' => false,
+ ],
+ ];
+
+ return $this->filter_media_files( $sizes );
+ }
+
+ /**
+ * If the media is an image, get its width and height.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_dimensions() {
+ if ( ! $this->is_image() ) {
+ return [
+ 'width' => 0,
+ 'height' => 0,
+ ];
+ }
+
+ $row = $this->get_row();
+
+ return [
+ 'width' => ! empty( $row['width'] ) ? $row['width'] : 0,
+ 'height' => ! empty( $row['height'] ) ? $row['height'] : 0,
+ ];
+ }
+
+ /**
+ * Update the media data dimensions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $dimensions {
+ * An array containing width and height.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ */
+ protected function update_media_data_dimensions( $dimensions ) {
+ $row = $this->get_row();
+
+ if ( ! is_array( $row ) ) {
+ $row = [];
+ }
+
+ if ( isset( $row['width'], $row['height'] ) && $row['width'] === $dimensions['width'] && $row['height'] === $dimensions['height'] ) {
+ return;
+ }
+
+ $row['width'] = $dimensions['width'];
+ $row['height'] = $dimensions['height'];
+
+ $this->update_row( $row );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Media/MediaInterface.php b/wp-content/plugins/imagify/classes/Media/MediaInterface.php
new file mode 100644
index 00000000..79be2780
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Media/MediaInterface.php
@@ -0,0 +1,337 @@
+ 0,
+ 'height' => 0,
+ ];
+ }
+
+ /**
+ * If the media is an image, update the dimensions in the database with the current file dimensions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool True on success. False on failure.
+ */
+ public function update_dimensions() {
+ return false;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Media/WP.php b/wp-content/plugins/imagify/classes/Media/WP.php
new file mode 100644
index 00000000..1bc3d767
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Media/WP.php
@@ -0,0 +1,430 @@
+post_type ) {
+ parent::__construct( 0 );
+ return;
+ }
+
+ parent::__construct( $id->ID );
+ }
+
+ /**
+ * Tell if the given entry can be accepted in the constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param mixed $id Whatever.
+ * @return bool
+ */
+ public static function constructor_accepts( $id ) {
+ return $id && ( is_numeric( $id ) || $id instanceof \WP_Post );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** ORIGINAL FILE =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the original file path, even if the file doesn't exist.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_original_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_path( 'original' );
+ }
+
+ if ( $this->is_wp_53() ) {
+ // `wp_get_original_image_path()` may return false.
+ $path = wp_get_original_image_path( $this->id );
+ } else {
+ $path = false;
+ }
+
+ if ( ! $path ) {
+ $path = get_attached_file( $this->id );
+ }
+
+ return $path ? $path : false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** FULL SIZE FILE ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the URL of the mediaâs full size file.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file URL. False on failure.
+ */
+ public function get_fullsize_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_url();
+ }
+
+ $url = wp_get_attachment_url( $this->id );
+
+ return $url ? $url : false;
+ }
+
+ /**
+ * Get the path to the mediaâs full size file, even if the file doesn't exist.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_fullsize_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_path();
+ }
+
+ $path = get_attached_file( $this->id );
+
+ return $path ? $path : false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BACKUP FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the backup URL, even if the file doesn't exist.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file URL. False on failure.
+ */
+ public function get_backup_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return get_imagify_attachment_url( $this->get_raw_backup_path() );
+ }
+
+ /**
+ * Get the backup file path, even if the file doesn't exist.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_backup_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return get_imagify_attachment_backup_path( $this->get_raw_original_path() );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** THUMBNAILS ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Create the media thumbnails.
+ * With WP 5.3+, this will also generate a new full size file if the original file is wider or taller than a defined threshold.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True on success. A \WP_Error instance on failure.
+ */
+ public function generate_thumbnails() {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/image.php';
+ }
+
+ // Store the path to the current full size file before generating the thumbnails.
+ $old_full_size_path = $this->get_raw_fullsize_path();
+ $metadata = wp_generate_attachment_metadata( $this->get_id(), $this->get_raw_original_path() );
+
+ if ( empty( $metadata['file'] ) ) {
+ // Σ(ï¾Ðï¾).
+ update_post_meta( $this->get_id(), '_wp_attachment_metadata', $metadata );
+
+ return true;
+ }
+
+ /**
+ * Don't change the full size file name.
+ * WP 5.3+ will rename the full size file if the resizing threshold has changed (not the same as the one used to generate it previously).
+ * This will force WP to keep the previous file name.
+ */
+ $old_full_size_file_name = $this->filesystem->file_name( $old_full_size_path );
+ $new_full_size_file_name = $this->filesystem->file_name( $metadata['file'] );
+
+ if ( $new_full_size_file_name !== $old_full_size_file_name ) {
+ $new_full_size_path = $this->filesystem->dir_path( $old_full_size_path ) . $new_full_size_file_name;
+
+ $moved = $this->filesystem->move( $new_full_size_path, $old_full_size_path, true );
+
+ if ( $moved ) {
+ $metadata['file'] = $this->filesystem->dir_path( $metadata['file'] ) . $old_full_size_file_name;
+ update_post_meta( $this->get_id(), '_wp_attached_file', $metadata['file'] );
+ }
+ }
+
+ update_post_meta( $this->get_id(), '_wp_attachment_metadata', $metadata );
+
+ return true;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** MEDIA DATA ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if the current media has the required data (the data containing the file paths and thumbnails).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_required_media_data() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ $file = get_post_meta( $this->id, '_wp_attached_file', true );
+
+ if ( ! $file || preg_match( '@://@', $file ) || preg_match( '@^.:\\\@', $file ) ) {
+ return false;
+ }
+
+ return (bool) wp_get_attachment_metadata( $this->id, true );
+ }
+
+ /**
+ * Get the list of the files of this media, including the full size file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * An array with the size names as keys ('full' is used for the full size file), and arrays of data as values:
+ *
+ * @type string $size The size name.
+ * @type string $path Absolute path to the file.
+ * @type int $width The file width.
+ * @type int $height The file height.
+ * @type string $mime-type The file mime type.
+ * @type bool $disabled True if the size is disabled in the pluginâs settings.
+ * }
+ */
+ public function get_media_files() {
+ if ( ! $this->is_valid() ) {
+ return [];
+ }
+
+ $fullsize_path = $this->get_raw_fullsize_path();
+
+ if ( ! $fullsize_path ) {
+ return [];
+ }
+
+ $dimensions = $this->get_dimensions();
+ $all_sizes = [
+ 'full' => [
+ 'size' => 'full',
+ 'path' => $fullsize_path,
+ 'width' => $dimensions['width'],
+ 'height' => $dimensions['height'],
+ 'mime-type' => $this->get_mime_type(),
+ 'disabled' => false,
+ ],
+ ];
+
+ if ( $this->is_image() ) {
+ $sizes = wp_get_attachment_metadata( $this->id, true );
+ $sizes = ! empty( $sizes['sizes'] ) && is_array( $sizes['sizes'] ) ? $sizes['sizes'] : [];
+ $sizes = array_intersect_key( $sizes, $this->get_context_instance()->get_thumbnail_sizes() );
+ } else {
+ $sizes = [];
+ }
+
+ if ( ! $sizes ) {
+ return $all_sizes;
+ }
+
+ $dir_path = $this->filesystem->dir_path( $fullsize_path );
+ $disallowed_sizes = get_imagify_option( 'disallowed-sizes' );
+ $is_active_for_network = imagify_is_active_for_network();
+
+ foreach ( $sizes as $size => $size_data ) {
+ $all_sizes[ $size ] = [
+ 'size' => $size,
+ 'path' => $dir_path . $size_data['file'],
+ 'width' => $size_data['width'],
+ 'height' => $size_data['height'],
+ 'mime-type' => $size_data['mime-type'],
+ 'disabled' => ! $is_active_for_network && isset( $disallowed_sizes[ $size ] ),
+ ];
+ }
+
+ return $this->filter_media_files( $all_sizes );
+ }
+
+ /**
+ * If the media is an image, get its width and height.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_dimensions() {
+ if ( ! $this->is_image() ) {
+ return [
+ 'width' => 0,
+ 'height' => 0,
+ ];
+ }
+
+ $values = wp_get_attachment_image_src( $this->id, 'full' );
+
+ return [
+ 'width' => $values[1],
+ 'height' => $values[2],
+ ];
+ }
+
+ /**
+ * Update the media data dimensions.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $dimensions {
+ * An array containing width and height.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ */
+ protected function update_media_data_dimensions( $dimensions ) {
+ $metadata = wp_get_attachment_metadata( $this->id );
+
+ if ( ! is_array( $metadata ) ) {
+ $row = [];
+ }
+
+ if ( isset( $metadata['width'], $metadata['height'] ) && $metadata['width'] === $dimensions['width'] && $metadata['height'] === $dimensions['height'] ) {
+ return;
+ }
+
+ $metadata['width'] = $dimensions['width'];
+ $metadata['height'] = $dimensions['height'];
+
+ update_post_meta( $this->get_id(), '_wp_attachment_metadata', $metadata );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** INTERNAL TOOLS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if weâre playing in WP 5.3âs garden.
+ *
+ * @since 1.9.8
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ protected function is_wp_53() {
+ if ( isset( $this->is_wp53 ) ) {
+ return $this->is_wp53;
+ }
+
+ $this->is_wp53 = function_exists( 'wp_get_original_image_path' );
+
+ return $this->is_wp53;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Data/AbstractData.php b/wp-content/plugins/imagify/classes/Optimization/Data/AbstractData.php
new file mode 100644
index 00000000..13168d50
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Data/AbstractData.php
@@ -0,0 +1,451 @@
+get_optimization_data()
+ * @author Grégory Viguier
+ */
+ protected $default_optimization_data = [
+ 'status' => '',
+ 'level' => false,
+ 'sizes' => [],
+ 'stats' => [
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ ],
+ ];
+
+ /**
+ * The media object.
+ *
+ * @var MediaInterface
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $media;
+
+ /**
+ * Filesystem object.
+ *
+ * @var object Imagify_Filesystem
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $filesystem;
+
+ /**
+ * The constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @see self::constructor_accepts()
+ * @author Grégory Viguier
+ *
+ * @param mixed $id An ID, or whatever type the constructor accepts.
+ */
+ public function __construct( $id ) {
+ // Set the Media instance.
+ if ( $id instanceof MediaInterface ) {
+ $this->media = $id;
+ } elseif ( static::constructor_accepts( $id ) ) {
+ $media_class = str_replace( '\\Optimization\\Data\\', '\\Media\\', get_called_class() );
+ $media_class = '\\' . ltrim( $media_class, '\\' );
+ $this->media = new $media_class( $id );
+ } else {
+ $this->media = false;
+ }
+
+ $this->filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Tell if the given entry can be accepted in the constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param mixed $id Whatever.
+ * @return bool
+ */
+ public static function constructor_accepts( $id ) {
+ if ( $id instanceof MediaInterface ) {
+ return true;
+ }
+
+ $media_class = str_replace( '\\Optimization\\Data\\', '\\Media\\', get_called_class() );
+ $media_class = '\\' . ltrim( $media_class, '\\' );
+
+ return $media_class::constructor_accepts( $id );
+ }
+
+ /**
+ * Get the media instance.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return MediaInterface|false
+ */
+ public function get_media() {
+ return $this->media;
+ }
+
+ /**
+ * Tell if the current media is valid.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_valid() {
+ return $this->get_media() && $this->get_media()->is_valid();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION DATA ======================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Check if the main file is optimized (by Imagify).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool True if the media is optimized.
+ */
+ public function is_optimized() {
+ return 'success' === $this->get_optimization_status();
+ }
+
+ /**
+ * Check if the main file is optimized (NOT by Imagify).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool True if the media is optimized.
+ */
+ public function is_already_optimized() {
+ return 'already_optimized' === $this->get_optimization_status();
+ }
+
+ /**
+ * Check if the main file is optimized (by Imagify).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool True if the media is optimized.
+ */
+ public function is_error() {
+ return 'error' === $this->get_optimization_status();
+ }
+
+ /**
+ * Get the media's optimization level.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int|false The optimization level. False if not optimized.
+ */
+ public function get_optimization_level() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ $data = $this->get_optimization_data();
+ return $data['level'];
+ }
+
+ /**
+ * Get the media's optimization status (success or error).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string The optimization status. An empty string if there is none.
+ */
+ public function get_optimization_status() {
+ if ( ! $this->is_valid() ) {
+ return '';
+ }
+
+ $data = $this->get_optimization_data();
+ return $data['status'];
+ }
+
+ /**
+ * Count number of optimized sizes.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int Number of optimized sizes.
+ */
+ public function get_optimized_sizes_count() {
+ $data = $this->get_optimization_data();
+ $count = 0;
+
+ if ( ! $data['sizes'] ) {
+ return 0;
+ }
+
+ $context_sizes = $this->get_media()->get_media_files();
+ $data['sizes'] = array_intersect_key( $data['sizes'], $context_sizes );
+
+ if ( ! $data['sizes'] ) {
+ return 0;
+ }
+
+ foreach ( $data['sizes'] as $size ) {
+ if ( ! empty( $size['success'] ) ) {
+ $count++;
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Get the original media's size (weight).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $human_format True to display the image human format size (1Mb).
+ * @param int $decimals Precision of number of decimal places.
+ * @return string|int
+ */
+ public function get_original_size( $human_format = true, $decimals = 2 ) {
+ if ( ! $this->is_valid() ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+ $size = $this->get_optimization_data();
+ $size = ! empty( $size['sizes']['full']['original_size'] ) ? $size['sizes']['full']['original_size'] : 0;
+
+ // If nothing in the database, try to get the info from the file.
+ if ( ! $size ) {
+ // Check for the backup file first.
+ $filepath = $this->get_media()->get_backup_path();
+
+ if ( ! $filepath ) {
+ // Try the original file then.
+ $filepath = $this->get_media()->get_original_path();
+ }
+
+ $size = $filepath ? $this->filesystem->size( $filepath ) : 0;
+ }
+
+ if ( $human_format ) {
+ return imagify_size_format( (int) $size, $decimals );
+ }
+
+ return (int) $size;
+ }
+
+ /**
+ * Get the file size of the full size file.
+ * If the webp size is available, it is used.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $human_format True to display the image human format size (1Mb).
+ * @param int $decimals Precision of number of decimal places.
+ * @param bool $use_webp Use the webp size if available.
+ * @return string|int
+ */
+ public function get_optimized_size( $human_format = true, $decimals = 2, $use_webp = true ) {
+ if ( ! $this->is_valid() ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+ $data = $this->get_optimization_data();
+ $media = $this->get_media();
+
+ if ( $use_webp ) {
+ $process_class_name = imagify_get_optimization_process_class_name( $media->get_context() );
+ $webp_size_name = 'full' . constant( $process_class_name . '::WEBP_SUFFIX' );
+ }
+
+ if ( $use_webp && ! empty( $data['sizes'][ $webp_size_name ]['optimized_size'] ) ) {
+ $size = (int) $data['sizes'][ $webp_size_name ]['optimized_size'];
+ } elseif ( ! empty( $data['sizes']['full']['optimized_size'] ) ) {
+ $size = (int) $data['sizes']['full']['optimized_size'];
+ } else {
+ $size = 0;
+ }
+
+ if ( $size ) {
+ return $human_format ? imagify_size_format( $size, $decimals ) : $size;
+ }
+
+ // If nothing in the database, try to get the info from the file.
+ $filepath = false;
+
+ if ( $use_webp && ! empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
+ // Try with the webp file first.
+ $filepath = $media->get_raw_fullsize_path();
+ $filepath = $filepath ? imagify_path_to_webp( $filepath ) : false;
+
+ if ( ! $filepath || ! $this->filesystem->exists( $filepath ) ) {
+ $filepath = false;
+ }
+ }
+
+ if ( ! $filepath ) {
+ // No webp? The full size then.
+ $filepath = $media->get_fullsize_path();
+ }
+
+ if ( ! $filepath ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+ $size = (int) $this->filesystem->size( $filepath );
+
+ return $human_format ? imagify_size_format( $size, $decimals ) : $size;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION STATS ====================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get one or all statistics of a specific size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The thumbnail slug.
+ * @param string $key The specific data slug.
+ * @return array|string
+ */
+ public function get_size_data( $size = 'full', $key = '' ) {
+ $data = $this->get_optimization_data();
+
+ if ( ! isset( $data['sizes'][ $size ] ) ) {
+ return $key ? '' : [];
+ }
+
+ if ( ! $key ) {
+ return $data['sizes'][ $size ];
+ }
+
+ if ( ! isset( $data['sizes'][ $size ][ $key ] ) ) {
+ return '';
+ }
+
+ return $data['sizes'][ $size ][ $key ];
+ }
+
+ /**
+ * Get the overall statistics data or a specific one.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $key The specific data slug.
+ * @return array|string
+ */
+ public function get_stats_data( $key = '' ) {
+ $data = $this->get_optimization_data();
+ $stats = '';
+
+ if ( empty( $data['stats'] ) ) {
+ return $key ? '' : [];
+ }
+
+ if ( ! isset( $data['stats'][ $key ] ) ) {
+ return '';
+ }
+
+ return $data['stats'][ $key ];
+ }
+
+ /**
+ * Get the optimized/original saving of the original image in percent.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return float A 2-decimals float.
+ */
+ public function get_saving_percent() {
+ if ( ! $this->is_valid() ) {
+ return round( (float) 0, 2 );
+ }
+
+ $process_class_name = imagify_get_optimization_process_class_name( $this->get_media()->get_context() );
+ $webp_size_name = 'full' . constant( $process_class_name . '::WEBP_SUFFIX' );
+
+ $percent = $this->get_size_data( $webp_size_name, 'percent' );
+
+ if ( ! $percent ) {
+ $percent = $this->get_size_data( 'full', 'percent' );
+ }
+
+ $percent = $percent ? $percent : 0;
+
+ return round( (float) $percent, 2 );
+ }
+
+ /**
+ * Get the overall optimized/original saving (original image + all thumbnails) in percent.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return float A 2-decimals float.
+ */
+ public function get_overall_saving_percent() {
+ if ( ! $this->is_valid() ) {
+ return round( (float) 0, 2 );
+ }
+
+ $percent = $this->get_stats_data( 'percent' );
+
+ return round( (float) $percent, 2 );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Data/CustomFolders.php b/wp-content/plugins/imagify/classes/Optimization/Data/CustomFolders.php
new file mode 100644
index 00000000..1ba511c9
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Data/CustomFolders.php
@@ -0,0 +1,316 @@
+is_valid() ) {
+ return;
+ }
+
+ // This is required by MediaRowTrait.
+ $this->id = $this->get_media()->get_id();
+
+ // In this context, the media data and the optimization data are stored in the same DB table, so, no need to request twice the DB.
+ $this->row = $this->get_media()->get_row();
+ }
+
+ /**
+ * Get the whole media optimization data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array The data. See parent method for details.
+ */
+ public function get_optimization_data() {
+ if ( ! $this->is_valid() ) {
+ return $this->default_optimization_data;
+ }
+
+ $row = array_merge( $this->get_row_db_instance()->get_column_defaults(), $this->get_row() );
+ $data = $this->default_optimization_data;
+
+ $data['status'] = $row['status'];
+ $data['level'] = $row['optimization_level'];
+ $data['level'] = is_numeric( $data['level'] ) ? (int) $data['level'] : false;
+
+ if ( 'success' === $row['status'] ) {
+ /**
+ * Success.
+ */
+ $data['sizes']['full'] = [
+ 'success' => true,
+ 'original_size' => $row['original_size'],
+ 'optimized_size' => $row['optimized_size'],
+ 'percent' => $row['percent'],
+ ];
+ } elseif ( ! empty( $row['status'] ) ) {
+ /**
+ * Error.
+ */
+ $data['sizes']['full'] = [
+ 'success' => false,
+ 'error' => $row['error'],
+ ];
+ }
+
+ if ( ! empty( $row['data']['sizes'] ) && is_array( $row['data']['sizes'] ) ) {
+ unset( $row['data']['sizes']['full'] );
+ $data['sizes'] = array_merge( $data['sizes'], $row['data']['sizes'] );
+ $data['sizes'] = array_filter( $data['sizes'], 'is_array' );
+ }
+
+ if ( empty( $data['sizes'] ) ) {
+ return $data;
+ }
+
+ foreach ( $data['sizes'] as $size_data ) {
+ // Cast.
+ if ( isset( $size_data['original_size'] ) ) {
+ $size_data['original_size'] = (int) $size_data['original_size'];
+ }
+ if ( isset( $size_data['optimized_size'] ) ) {
+ $size_data['optimized_size'] = (int) $size_data['optimized_size'];
+ }
+ if ( isset( $size_data['percent'] ) ) {
+ $size_data['percent'] = round( $size_data['percent'], 2 );
+ }
+ // Stats.
+ if ( ! empty( $size_data['original_size'] ) && ! empty( $size_data['optimized_size'] ) ) {
+ $data['stats']['original_size'] += $size_data['original_size'];
+ $data['stats']['optimized_size'] += $size_data['optimized_size'];
+ }
+ }
+
+ if ( $data['stats']['original_size'] && $data['stats']['optimized_size'] ) {
+ $data['stats']['percent'] = $data['stats']['original_size'] - $data['stats']['optimized_size'];
+ $data['stats']['percent'] = round( $data['stats']['percent'] / $data['stats']['original_size'] * 100, 2 );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Update the optimization data, level, and status for a size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @param array $data The optimization data. See parent method for details.
+ */
+ public function update_size_optimization_data( $size, array $data ) {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ $old_data = array_merge( $this->get_reset_data(), $this->get_row() );
+
+ if ( 'full' === $size ) {
+ /**
+ * Original file.
+ */
+ $old_data['optimization_level'] = $data['level'];
+ $old_data['status'] = $data['status'];
+ $old_data['modified'] = 0;
+
+ $file_path = $this->get_media()->get_fullsize_path();
+
+ if ( $file_path ) {
+ $old_data['hash'] = md5_file( $file_path );
+ }
+
+ if ( ! $data['success'] ) {
+ /**
+ * Error.
+ */
+ $old_data['error'] = $data['error'];
+ } else {
+ /**
+ * Success.
+ */
+ $old_data['original_size'] = $data['original_size'];
+ $old_data['optimized_size'] = $data['optimized_size'];
+ $old_data['percent'] = $data['original_size'] - $data['optimized_size'];
+ $old_data['percent'] = round( ( $old_data['percent'] / $data['original_size'] ) * 100, 2 );
+ }
+ } else {
+ /**
+ * Webp version or any other size.
+ */
+ $old_data['data'] = ! empty( $old_data['data'] ) && is_array( $old_data['data'] ) ? $old_data['data'] : [];
+ $old_data['data']['sizes'] = ! empty( $old_data['data']['sizes'] ) && is_array( $old_data['data']['sizes'] ) ? $old_data['data']['sizes'] : [];
+
+ if ( ! $data['success'] ) {
+ /**
+ * Error.
+ */
+ $old_data['data']['sizes'][ $size ] = [
+ 'success' => false,
+ 'error' => $data['error'],
+ ];
+ } else {
+ /**
+ * Success.
+ */
+ $old_data['data']['sizes'][ $size ] = [
+ 'success' => true,
+ 'original_size' => $data['original_size'],
+ 'optimized_size' => $data['optimized_size'],
+ 'percent' => round( ( ( $data['original_size'] - $data['optimized_size'] ) / $data['original_size'] ) * 100, 2 ),
+ ];
+ }
+ }
+
+ if ( isset( $old_data['data']['sizes'] ) && ( ! $old_data['data']['sizes'] || ! is_array( $old_data['data']['sizes'] ) ) ) {
+ unset( $old_data['data']['sizes'] );
+ }
+
+ $this->update_row( $old_data );
+ }
+
+ /**
+ * Delete the media optimization data, level, and status.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_optimization_data() {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ $this->update_row( $this->get_reset_data() );
+ }
+
+ /**
+ * Delete the optimization data for the given sizes.
+ * If all sizes are removed, all optimization data is deleted.
+ * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes A list of sizes to remove.
+ */
+ public function delete_sizes_optimization_data( array $sizes ) {
+ if ( ! $sizes || ! $this->is_valid() ) {
+ return;
+ }
+
+ $data = array_merge( $this->get_reset_data(), $this->get_row() );
+
+ $data['data']['sizes'] = ! empty( $data['data']['sizes'] ) && is_array( $data['data']['sizes'] ) ? $data['data']['sizes'] : [];
+
+ if ( ! $data['data']['sizes'] ) {
+ return;
+ }
+
+ $remaining_sizes_data = array_diff_key( $data['data']['sizes'], array_flip( $sizes ) );
+
+ if ( ! $remaining_sizes_data ) {
+ // All sizes have been removed: delete everything.
+ $this->delete_optimization_data();
+ return;
+ }
+
+ if ( count( $remaining_sizes_data ) === count( $data['data']['sizes'] ) ) {
+ // Nothing has been removed.
+ return;
+ }
+
+ $data['data']['sizes'] = $remaining_sizes_data;
+
+ $this->update_row( $data );
+ }
+
+ /**
+ * Get default values used to reset optimization data.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * The default values related to the optimization.
+ *
+ * @type string $hash The file hash.
+ * @type int $modified 0 to tell that the file has not been modified
+ * @type int $optimized_size File size after optimization.
+ * @type int $percent Saving optimized/original in percent.
+ * @type int $optimization_level The optimization level.
+ * @type string $status The status: success, already_optimized, error.
+ * @type string $error An error message.
+ * }
+ */
+ protected function get_reset_data() {
+ static $column_defaults;
+
+ if ( ! isset( $column_defaults ) ) {
+ $column_defaults = $this->get_row_db_instance()->get_column_defaults();
+
+ // All DB columns that have `null` as default value, are Imagify data.
+ foreach ( $column_defaults as $column_name => $value ) {
+ if ( 'hash' === $column_name || 'modified' === $column_name || 'data' === $column_name ) {
+ continue;
+ }
+
+ if ( isset( $value ) ) {
+ unset( $column_defaults[ $column_name ] );
+ }
+ }
+ }
+
+ $imagify_columns = $column_defaults;
+
+ // Also set the new file hash.
+ $file_path = $this->get_media()->get_fullsize_path();
+
+ if ( $file_path ) {
+ $imagify_columns['hash'] = md5_file( $file_path );
+ }
+
+ return $imagify_columns;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Data/DataInterface.php b/wp-content/plugins/imagify/classes/Optimization/Data/DataInterface.php
new file mode 100644
index 00000000..b8f272d4
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Data/DataInterface.php
@@ -0,0 +1,275 @@
+ '',
+ 'level' => false,
+ 'sizes' => [],
+ 'stats' => [
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ ],
+ ];
+ }
+
+ /**
+ * Update the optimization data, level, and status for a size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @param array $data The optimization data. See parent method for details.
+ */
+ public function update_size_optimization_data( $size, array $data ) {}
+
+ /**
+ * Delete the media optimization data, level, and status.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_optimization_data() {}
+
+ /**
+ * Delete the optimization data for the given sizes.
+ * If all sizes are removed, all optimization data is deleted.
+ * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes A list of sizes to remove.
+ */
+ public function delete_sizes_optimization_data( array $sizes ) {}
+
+ /**
+ * Get the media's optimization level.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int|bool The optimization level. False if not optimized.
+ */
+ public function get_optimization_level() {
+ return false;
+ }
+
+ /**
+ * Get the media's optimization status (success or error).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string The optimization status. An empty string if there is none.
+ */
+ public function get_optimization_status() {
+ return '';
+ }
+
+ /**
+ * Count number of optimized sizes.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int Number of optimized sizes.
+ */
+ public function get_optimized_sizes_count() {
+ return 0;
+ }
+
+ /**
+ * Get the original media's size (weight).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $human_format True to display the image human format size (1Mb).
+ * @param int $decimals Precision of number of decimal places.
+ * @return string|int
+ */
+ public function get_original_size( $human_format = true, $decimals = 2 ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+ /**
+ * Get the file size of the full size file.
+ * If the webp size is available, it is used.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $human_format True to display the image human format size (1Mb).
+ * @param int $decimals Precision of number of decimal places.
+ * @param bool $use_webp Use the webp size if available.
+ * @return string|int
+ */
+ public function get_optimized_size( $human_format = true, $decimals = 2, $use_webp = true ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION STATS ====================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get one or all statistics of a specific size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The thumbnail slug.
+ * @param string $key The specific data slug.
+ * @return array|string
+ */
+ public function get_size_data( $size = 'full', $key = '' ) {
+ return $key ? '' : [];
+ }
+
+ /**
+ * Get the overall statistics data or a specific one.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $key The specific data slug.
+ * @return array|string
+ */
+ public function get_stats_data( $key = '' ) {
+ return $key ? '' : [];
+ }
+
+ /**
+ * Get the optimized/original saving of the original image in percent.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return float A 2-decimals float.
+ */
+ public function get_saving_percent() {
+ return round( (float) 0, 2 );
+ }
+
+ /**
+ * Get the overall optimized/original saving (original image + all thumbnails) in percent.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return float A 2-decimals float.
+ */
+ public function get_overall_saving_percent() {
+ return round( (float) 0, 2 );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Data/WP.php b/wp-content/plugins/imagify/classes/Optimization/Data/WP.php
new file mode 100644
index 00000000..220d4891
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Data/WP.php
@@ -0,0 +1,202 @@
+is_valid() ) {
+ return $this->default_optimization_data;
+ }
+
+ $id = $this->get_media()->get_id();
+
+ $data = get_post_meta( $id, '_imagify_data', true );
+ $data = is_array( $data ) ? $data : [];
+
+ if ( isset( $data['sizes'] ) && ! is_array( $data['sizes'] ) ) {
+ $data['sizes'] = [];
+ }
+
+ if ( isset( $data['stats'] ) && ! is_array( $data['stats'] ) ) {
+ $data['stats'] = [];
+ }
+
+ $data = array_merge( $this->default_optimization_data, $data );
+
+ $data['status'] = get_post_meta( $id, '_imagify_status', true );
+ $data['status'] = is_string( $data['status'] ) ? $data['status'] : '';
+
+ $data['level'] = get_post_meta( $id, '_imagify_optimization_level', true );
+ $data['level'] = is_numeric( $data['level'] ) ? (int) $data['level'] : false;
+
+ return $data;
+ }
+
+ /**
+ * Update the optimization data, level, and status for a size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @param array $data The optimization data. See parent method for details.
+ */
+ public function update_size_optimization_data( $size, array $data ) {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ $id = $this->get_media()->get_id();
+
+ if ( 'full' === $size ) {
+ // Optimization level.
+ update_post_meta( $id, '_imagify_optimization_level', $data['level'] );
+ // Optimization status.
+ update_post_meta( $id, '_imagify_status', $data['status'] );
+ }
+
+ // Size data and stats.
+ $old_data = get_post_meta( $id, '_imagify_data', true );
+ $old_data = is_array( $old_data ) ? $old_data : [];
+
+ if ( ! isset( $old_data['sizes'] ) || ! is_array( $old_data['sizes'] ) ) {
+ $old_data['sizes'] = [];
+ }
+
+ if ( ! isset( $old_data['stats'] ) || ! is_array( $old_data['stats'] ) ) {
+ $old_data['stats'] = [];
+ }
+
+ $old_data['stats'] = array_merge( [
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ ], $old_data['stats'] );
+
+ if ( ! $data['success'] ) {
+ /**
+ * Error.
+ */
+ $old_data['sizes'][ $size ] = [
+ 'success' => false,
+ 'error' => $data['error'],
+ ];
+ } else {
+ /**
+ * Success.
+ */
+ $old_data['sizes'][ $size ] = [
+ 'success' => true,
+ 'original_size' => $data['original_size'],
+ 'optimized_size' => $data['optimized_size'],
+ 'percent' => round( ( ( $data['original_size'] - $data['optimized_size'] ) / $data['original_size'] ) * 100, 2 ),
+ ];
+
+ $old_data['stats']['original_size'] += $data['original_size'];
+ $old_data['stats']['optimized_size'] += $data['optimized_size'];
+ $old_data['stats']['percent'] = round( ( ( $old_data['stats']['original_size'] - $old_data['stats']['optimized_size'] ) / $old_data['stats']['original_size'] ) * 100, 2 );
+ }
+
+ update_post_meta( $id, '_imagify_data', $old_data );
+ }
+
+ /**
+ * Delete the media optimization data, level, and status.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_optimization_data() {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ $id = $this->get_media()->get_id();
+
+ delete_post_meta( $id, '_imagify_data' );
+ delete_post_meta( $id, '_imagify_status' );
+ delete_post_meta( $id, '_imagify_optimization_level' );
+ }
+
+ /**
+ * Delete the optimization data for the given sizes.
+ * If all sizes are removed, all optimization data is deleted.
+ * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes A list of sizes to remove.
+ */
+ public function delete_sizes_optimization_data( array $sizes ) {
+ if ( ! $sizes || ! $this->is_valid() ) {
+ return;
+ }
+
+ $media_id = $this->get_media()->get_id();
+ $data = get_post_meta( $media_id, '_imagify_data', true );
+
+ if ( empty( $data['sizes'] ) || ! is_array( $data['sizes'] ) ) {
+ return;
+ }
+
+ $remaining_sizes_data = array_diff_key( $data['sizes'], array_flip( $sizes ) );
+
+ if ( ! $remaining_sizes_data ) {
+ // All sizes have been removed: delete everything.
+ $this->delete_optimization_data();
+ return;
+ }
+
+ if ( count( $remaining_sizes_data ) === count( $data['sizes'] ) ) {
+ // Nothing has been removed.
+ return;
+ }
+
+ $data['sizes'] = $remaining_sizes_data;
+
+ // Update stats.
+ $data['stats'] = [
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ ];
+
+ foreach ( $data['sizes'] as $size_data ) {
+ if ( empty( $size_data['success'] ) ) {
+ continue;
+ }
+
+ $data['stats']['original_size'] += $size_data['original_size'];
+ $data['stats']['optimized_size'] += $size_data['optimized_size'];
+ }
+
+ $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 );
+
+ update_post_meta( $media_id, '_imagify_data', $data );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/File.php b/wp-content/plugins/imagify/classes/Optimization/File.php
new file mode 100644
index 00000000..090caa34
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/File.php
@@ -0,0 +1,881 @@
+is_image()
+ * @author Grégory Viguier
+ */
+ protected $is_image;
+
+ /**
+ * Store the file mime type + file extension (if the file is supported).
+ *
+ * @var array
+ * @since 1.9
+ * @access protected
+ * @see $this->get_file_type()
+ * @author Grégory Viguier
+ */
+ protected $file_type;
+
+ /**
+ * Filesystem object.
+ *
+ * @var \Imagify_Filesystem
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $filesystem;
+
+ /**
+ * The editor instance used to resize the file.
+ *
+ * @var \WP_Image_Editor_Imagick|\WP_Image_Editor_GD|WP_Error.
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $editor;
+
+ /**
+ * Used to cache the pluginâs options.
+ *
+ * @var array
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $options = [];
+
+ /**
+ * The constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Absolute path to the file.
+ */
+ public function __construct( $file_path ) {
+ $this->path = $file_path;
+ $this->filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Tell if the file is valid.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_valid() {
+ return (bool) $this->path;
+ }
+
+ /**
+ * Tell if the file can be processed.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error
+ */
+ public function can_be_processed() {
+ if ( ! $this->path ) {
+ return new \WP_Error( 'empty_path', __( 'File path is empty.', 'imagify' ) );
+ }
+
+ if ( ! empty( $this->filesystem->errors->errors ) ) {
+ return new \WP_Error( 'filesystem_error', __( 'Filesystem error.', 'imagify' ), $this->filesystem->errors );
+ }
+
+ if ( ! $this->filesystem->exists( $this->path ) ) {
+ return new \WP_Error(
+ 'not_exists',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The file %s does not seem to exist.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . ''
+ )
+ );
+ }
+
+ if ( ! $this->filesystem->is_file( $this->path ) ) {
+ return new \WP_Error(
+ 'not_a_file',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'This does not seem to be a file: %s.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . ''
+ )
+ );
+ }
+
+ if ( ! $this->filesystem->is_writable( $this->path ) ) {
+ return new \WP_Error(
+ 'not_writable',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The file %s does not seem to be writable.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . ''
+ )
+ );
+ }
+
+ $parent_folder = $this->filesystem->dir_path( $this->path );
+
+ if ( ! $this->filesystem->is_writable( $parent_folder ) ) {
+ return new \WP_Error(
+ 'folder_not_writable',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The folder %s does not seem to be writable.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $parent_folder ) ) . ''
+ )
+ );
+ }
+
+ return true;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** EDITION ================================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Resize (and rotate) an image if it is bigger than the maximum width provided.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ * @author Remy Perona
+ *
+ * @param array $dimensions {
+ * Array of image dimensions.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ * @param int $max_width Maximum width to resize to.
+ * @return string|WP_Error Path the the resized image. A WP_Error object on failure.
+ */
+ public function resize( $dimensions = [], $max_width = 0 ) {
+ $can_be_processed = $this->can_be_processed();
+
+ if ( is_wp_error( $can_be_processed ) ) {
+ return $can_be_processed;
+ }
+
+ if ( ! $max_width ) {
+ return new \WP_Error(
+ 'no_resizing_threshold',
+ __( 'No threshold provided for resizing.', 'imagify' )
+ );
+ }
+
+ if ( ! $this->is_image() ) {
+ return new \WP_Error(
+ 'not_an_image',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The file %s does not seem to be an image, and cannot be resized.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . ''
+ )
+ );
+ }
+
+ $editor = $this->get_editor();
+
+ if ( is_wp_error( $editor ) ) {
+ return $editor;
+ }
+
+ // Try to correct the auto-rotation if the info is available.
+ if ( $this->filesystem->can_get_exif() && 'image/jpeg' === $this->get_mime_type() ) {
+ $exif = $this->filesystem->get_image_exif( $this->path );
+ $orientation = isset( $exif['Orientation'] ) ? (int) $exif['Orientation'] : 1;
+
+ switch ( $orientation ) {
+ case 2:
+ // Flip horizontally.
+ $editor->flip( true, false );
+ break;
+ case 3:
+ // Rotate 180 degrees or flip horizontally and vertically.
+ // Flipping seems faster/uses less resources.
+ $editor->flip( true, true );
+ break;
+ case 4:
+ // Flip vertically.
+ $editor->flip( false, true );
+ break;
+ case 5:
+ // Rotate 90 degrees counter-clockwise and flip vertically.
+ $result = $editor->rotate( 90 );
+
+ if ( ! is_wp_error( $result ) ) {
+ $editor->flip( false, true );
+ }
+ break;
+ case 6:
+ // Rotate 90 degrees clockwise (270 counter-clockwise).
+ $editor->rotate( 270 );
+ break;
+ case 7:
+ // Rotate 90 degrees counter-clockwise and flip horizontally.
+ $result = $editor->rotate( 90 );
+
+ if ( ! is_wp_error( $result ) ) {
+ $editor->flip( true, false );
+ }
+ break;
+ case 8:
+ // Rotate 90 degrees counter-clockwise.
+ $editor->rotate( 90 );
+ break;
+ }
+ }
+
+ if ( ! $dimensions ) {
+ $dimensions = $this->get_dimensions();
+ }
+
+ // Prevent removal of the exif data when resizing (only works with Imagick).
+ add_filter( 'image_strip_meta', '__return_false', 789 );
+
+ // Resize.
+ $new_sizes = wp_constrain_dimensions( $dimensions['width'], $dimensions['height'], $max_width );
+ $resized = $editor->resize( $new_sizes[0], $new_sizes[1], false );
+
+ // Remove the filter when we're done to prevent any conflict.
+ remove_filter( 'image_strip_meta', '__return_false', 789 );
+
+ if ( is_wp_error( $resized ) ) {
+ return $resized;
+ }
+
+ $resized_image_path = $editor->generate_filename( 'imagifyresized' );
+ $resized_image_saved = $editor->save( $resized_image_path );
+
+ if ( is_wp_error( $resized_image_saved ) ) {
+ return $resized_image_saved;
+ }
+
+ return $resized_image_path;
+ }
+
+ /**
+ * Create a thumbnail.
+ * Warning: If the destination file already exists, it will be overwritten.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $destination {
+ * The thumbnail data.
+ *
+ * @type string $path Path to the destination file.
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * @type bool $crop True to crop, false to resize.
+ * @type bool $adjust_filename True to adjust the file name like what `$editor->multi_resize()` returns, like WP default behavior (default). False to prevent it, and use the file name from $path instead.
+ * }
+ * @return bool|array|WP_Error {
+ * A WP_Error object on error. True if the file exists.
+ * An array of thumbnail data if the file has just been created:
+ *
+ * @type string $file File name.
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * @type string $mime-type The mime type.
+ * }
+ */
+ public function create_thumbnail( $destination ) {
+ $can_be_processed = $this->can_be_processed();
+
+ if ( is_wp_error( $can_be_processed ) ) {
+ return $can_be_processed;
+ }
+
+ if ( ! $this->is_image() ) {
+ return new \WP_Error(
+ 'not_an_image',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The file %s does not seem to be an image, and cannot be resized.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . ''
+ )
+ );
+ }
+
+ $editor = $this->get_editor();
+
+ if ( is_wp_error( $editor ) ) {
+ return $editor;
+ }
+
+ // Create the file.
+ $result = $editor->multi_resize( [ $destination ] );
+
+ if ( ! $result ) {
+ return new \WP_Error( 'image_resize_error', __( 'The thumbnail could not be created.', 'imagify' ) );
+ }
+
+ $result = reset( $result );
+
+ $filename = $result['file'];
+ $source_thumb_path = $this->filesystem->dir_path( $this->path ) . $filename;
+
+ if ( ! isset( $destination['adjust_filename'] ) || $destination['adjust_filename'] ) {
+ // The file name can change from what we expected (1px wider, etc), let's use the resulting data to move the file to the right place.
+ $destination_thumb_path = $this->filesystem->dir_path( $destination['path'] ) . $filename;
+ } else {
+ // Respect what is set in $path.
+ $destination_thumb_path = $destination['path'];
+ $result['file'] = $this->filesystem->file_name( $destination['path'] );
+ }
+
+ if ( $source_thumb_path === $destination_thumb_path ) {
+ return $result;
+ }
+
+ $moved = $this->filesystem->move( $source_thumb_path, $destination_thumb_path, true );
+
+ if ( ! $moved ) {
+ return new \WP_Error( 'move_error', __( 'The file could not be moved to its final destination.', 'imagify' ) );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Backup a file.
+ *
+ * @since 1.9
+ * @since 1.9.8 Added $backup_source argument.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $backup_path The backup path.
+ * @param string $backup_source Path to the file to backup. This is useful in WP 5.3+ when we want to optimize the full size: in that case we need to backup the original file.
+ * @return bool|WP_Error True on success. False if the backup option is disabled. A WP_Error object on failure.
+ */
+ public function backup( $backup_path = null, $backup_source = null ) {
+ $can_be_processed = $this->can_be_processed();
+
+ if ( is_wp_error( $can_be_processed ) ) {
+ return $can_be_processed;
+ }
+
+ // Make sure the backups directory has no errors.
+ if ( ! $backup_path ) {
+ return new \WP_Error( 'wp_upload_error', __( 'Error while retrieving the backups directory path.', 'imagify' ) );
+ }
+
+ // Create sub-directories.
+ $created = $this->filesystem->make_dir( $this->filesystem->dir_path( $backup_path ) );
+
+ if ( ! $created ) {
+ return new \WP_Error( 'backup_dir_not_writable', __( 'The backup directory is not writable.', 'imagify' ) );
+ }
+
+ $path = $backup_source && $this->filesystem->exists( $backup_source ) ? $backup_source : $this->path;
+
+ /**
+ * Allow to overwrite the backup file if it already exists.
+ *
+ * @since 1.6.9
+ * @author Grégory Viguier
+ *
+ * @param bool $overwrite Whether to overwrite the backup file.
+ * @param string $path The file path.
+ * @param string $backup_path The backup path.
+ */
+ $overwrite = apply_filters( 'imagify_backup_overwrite_backup', false, $path, $backup_path );
+
+ // Copy the file.
+ $this->filesystem->copy( $path, $backup_path, $overwrite, FS_CHMOD_FILE );
+
+ // Make sure the backup copy exists.
+ if ( ! $this->filesystem->exists( $backup_path ) ) {
+ return new \WP_Error( 'backup_doesnt_exist', __( 'The file could not be saved.', 'imagify' ), array(
+ 'file_path' => $this->filesystem->make_path_relative( $path ),
+ 'backup_path' => $this->filesystem->make_path_relative( $backup_path ),
+ ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * Optimize a file with Imagify.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $args {
+ * Optional. An array of arguments.
+ *
+ * @type bool $backup False to prevent backup. True to follow the user's setting. A backup can't be forced.
+ * @type string $backup_path If a backup must be done, this is the path to use. Default is the backup path used for the WP Media Library.
+ * @type int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @type bool $keep_exif To keep exif data or not.
+ * @type string $convert Set to 'webp' to convert the image to webp.
+ * @type string $context The context.
+ * @type int $original_size The file size, sent to the API.
+ * }
+ * @return \sdtClass|\WP_Error Optimized image data. A \WP_Error object on error.
+ */
+ public function optimize( $args = [] ) {
+ $args = array_merge( [
+ 'backup' => true,
+ 'backup_path' => null,
+ 'backup_source' => null,
+ 'optimization_level' => 0,
+ 'keep_exif' => true,
+ 'convert' => '',
+ 'context' => 'wp',
+ 'original_size' => 0,
+ ], $args );
+
+ $can_be_processed = $this->can_be_processed();
+
+ if ( is_wp_error( $can_be_processed ) ) {
+ return $can_be_processed;
+ }
+
+ // Check if external HTTP requests are blocked.
+ if ( Imagify_Requirements::is_imagify_blocked() ) {
+ return new \WP_Error( 'http_block_external', __( 'External HTTP requests are blocked.', 'imagify' ) );
+ }
+
+ /**
+ * Fires before a media file optimization.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $path Absolute path to the media file.
+ * @param array $args Arguments passed to the method.
+ */
+ do_action( 'imagify_before_optimize_file', $this->path, $args );
+
+ /**
+ * Fires before to optimize the Image with Imagify.
+ *
+ * @since 1.0
+ * @deprecated
+ *
+ * @param string $path Absolute path to the image file.
+ * @param bool $backup True if a backup will be make.
+ */
+ do_action_deprecated( 'before_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' );
+
+ if ( $args['backup'] ) {
+ $backup_result = $this->backup( $args['backup_path'], $args['backup_source'] );
+
+ if ( is_wp_error( $backup_result ) ) {
+ // Stop the process if we can't backup the file.
+ return $backup_result;
+ }
+ }
+
+ // Send file for optimization and fetch the response.
+ $data = [
+ 'normal' => 0 === $args['optimization_level'],
+ 'aggressive' => 1 === $args['optimization_level'],
+ 'ultra' => 2 === $args['optimization_level'],
+ 'keep_exif' => $args['keep_exif'],
+ 'original_size' => $args['original_size'],
+ 'context' => $args['context'],
+ ];
+
+ if ( $args['convert'] ) {
+ $data['convert'] = $args['convert'];
+ }
+
+ $response = upload_imagify_image( [
+ 'image' => $this->path,
+ 'data' => wp_json_encode( $data ),
+ ] );
+
+ if ( is_wp_error( $response ) ) {
+ return new \WP_Error( 'api_error', $response->get_error_message() );
+ }
+
+ if ( ! function_exists( 'download_url' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ }
+
+ $temp_file = download_url( $response->image );
+
+ if ( is_wp_error( $temp_file ) ) {
+ return new \WP_Error( 'temp_file_not_found', $temp_file->get_error_message() );
+ }
+
+ if ( 'webp' === $args['convert'] ) {
+ $destination_path = $this->get_path_to_webp();
+ $this->path = $destination_path;
+ $this->file_type = null;
+ $this->editor = null;
+ } else {
+ $destination_path = $this->path;
+ }
+
+ $moved = $this->filesystem->move( $temp_file, $destination_path, true );
+
+ if ( ! $moved ) {
+ return new \WP_Error( 'move_error', __( 'The file could not be moved to its final destination.', 'imagify' ) );
+ }
+
+ /**
+ * Fires after to optimize the Image with Imagify.
+ *
+ * @since 1.0
+ * @deprecated
+ *
+ * @param string $path Absolute path to the image file.
+ * @param bool $backup True if a backup has been made.
+ */
+ do_action_deprecated( 'after_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' );
+
+ /**
+ * Fires after a media file optimization.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $path Absolute path to the media file.
+ * @param array $args Arguments passed to the method.
+ */
+ do_action( 'imagify_after_optimize_file', $this->path, $args );
+
+ return $response;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** IMAGE EDITOR (GD/IMAGEMAGICK) =========================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get an image editor instance (WP_Image_Editor_Imagick, WP_Image_Editor_GD).
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return WP_Image_Editor_Imagick|WP_Image_Editor_GD|WP_Error
+ */
+ protected function get_editor() {
+ if ( isset( $this->editor ) ) {
+ return $this->editor;
+ }
+
+ $this->editor = wp_get_image_editor( $this->path, [
+ 'methods' => $this->get_editor_methods(),
+ ] );
+
+ if ( ! is_wp_error( $this->editor ) ) {
+ return $this->editor;
+ }
+
+ $this->editor = new \WP_Error(
+ 'image_editor',
+ sprintf(
+ /* translators: %1$s is an error message, %2$s is a "More info?" link. */
+ __( 'No php extensions are available to edit images on the server. ImageMagick or GD is required. The internal error is: %1$s. %2$s', 'imagify' ),
+ $this->editor->get_error_message(),
+ '' . __( 'More info?', 'imagify' ) . ' '
+ )
+ );
+
+ return $this->editor;
+ }
+
+ /**
+ * Get the image editor methods we will use.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ protected function get_editor_methods() {
+ static $methods;
+
+ if ( isset( $methods ) ) {
+ return $methods;
+ }
+
+ $methods = [
+ 'resize',
+ 'multi_resize',
+ 'generate_filename',
+ 'save',
+ ];
+
+ if ( $this->filesystem->can_get_exif() ) {
+ $methods[] = 'rotate';
+ }
+
+ return $methods;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** VARIOUS TOOLS =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Check if a file exceeds the weight limit (> 5mo).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_exceeded() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ $size = $this->filesystem->size( $this->path );
+
+ return $size > IMAGIFY_MAX_BYTES;
+ }
+
+ /**
+ * Tell if the current file is supported for a given context.
+ *
+ * @since 1.9
+ * @access public
+ * @see imagify_get_mime_types()
+ * @author Grégory Viguier
+ *
+ * @param array $allowed_mime_types A list of allowed mime types.
+ * @return bool
+ */
+ public function is_supported( $allowed_mime_types ) {
+ return in_array( $this->get_mime_type(), $allowed_mime_types, true );
+ }
+
+ /**
+ * Tell if the file is an image.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_image() {
+ if ( isset( $this->is_image ) ) {
+ return $this->is_image;
+ }
+
+ $this->is_image = strpos( $this->get_mime_type(), 'image/' ) === 0;
+
+ return $this->is_image;
+ }
+
+ /**
+ * Tell if the file is a pdf.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_pdf() {
+ return 'application/pdf' === $this->get_mime_type();
+ }
+
+ /**
+ * Get the file mime type.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_mime_type() {
+ return $this->get_file_type()->type;
+ }
+
+ /**
+ * Get the file extension.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|null
+ */
+ public function get_extension() {
+ return $this->get_file_type()->ext;
+ }
+
+ /**
+ * Get the file path.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_path() {
+ return $this->path;
+ }
+
+ /**
+ * Replace the file extension by webp.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path on success. False if not an image or on failure.
+ */
+ public function get_path_to_webp() {
+ if ( ! $this->is_image() ) {
+ return false;
+ }
+
+ if ( $this->is_webp() ) {
+ return false;
+ }
+
+ return imagify_path_to_webp( $this->path );
+ }
+
+ /**
+ * Tell if the file is a webp image.
+ * Rejects "path/to/.webp" files.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_webp() {
+ return preg_match( '@(?!^|/|\\\)\.webp$@i', $this->path );
+ }
+
+ /**
+ * Get the file mime type + file extension.
+ *
+ * @since 1.9
+ * @access protected
+ * @see wp_check_filetype()
+ * @author Grégory Viguier
+ *
+ * @return object {
+ * @type string $ext The file extension.
+ * @type string $type The mime type.
+ * }
+ */
+ protected function get_file_type() {
+ if ( isset( $this->file_type ) ) {
+ return $this->file_type;
+ }
+
+ $this->file_type = (object) [
+ 'ext' => '',
+ 'type' => '',
+ ];
+
+ if ( ! $this->is_valid() ) {
+ return $this->file_type;
+ }
+
+ $this->file_type = (object) wp_check_filetype( $this->path );
+
+ return $this->file_type;
+ }
+
+ /**
+ * If the media is an image, get its width and height.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_dimensions() {
+ if ( ! $this->is_image() ) {
+ return [
+ 'width' => 0,
+ 'height' => 0,
+ ];
+ }
+
+ $values = $this->filesystem->get_image_size( $this->path );
+
+ return [
+ 'width' => $values['width'],
+ 'height' => $values['height'],
+ ];
+ }
+
+ /**
+ * Get a pluginâs option.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $option_name The option nme.
+ * @return mixed
+ */
+ protected function get_option( $option_name ) {
+ if ( isset( $this->options[ $option_name ] ) ) {
+ return $this->options[ $option_name ];
+ }
+
+ $this->options[ $option_name ] = get_imagify_option( $option_name );
+
+ return $this->options[ $option_name ];
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Process/AbstractProcess.php b/wp-content/plugins/imagify/classes/Optimization/Process/AbstractProcess.php
new file mode 100644
index 00000000..31145dee
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Process/AbstractProcess.php
@@ -0,0 +1,1956 @@
+ null,
+ 'status' => null,
+ 'success' => null,
+ 'error' => null,
+ 'original_size' => null,
+ 'optimized_size' => null,
+ ];
+
+ /**
+ * A File instance.
+ *
+ * @var File
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $file;
+
+ /**
+ * Filesystem object.
+ *
+ * @var Imagify_Filesystem
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $filesystem;
+
+ /**
+ * Used to cache the pluginâs options.
+ *
+ * @var array
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $options = [];
+
+ /**
+ * The constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @see self::constructor_accepts()
+ * @author Grégory Viguier
+ *
+ * @param mixed $id An ID, or whatever type the constructor accepts.
+ */
+ public function __construct( $id ) {
+ if ( $id instanceof DataInterface ) {
+ $this->data = $id;
+ } elseif ( static::constructor_accepts( $id ) ) {
+ $data_class = str_replace( '\\Optimization\\Process\\', '\\Optimization\\Data\\', get_called_class() );
+ $data_class = '\\' . ltrim( $data_class, '\\' );
+ $this->data = new $data_class( $id );
+ } else {
+ $this->data = false;
+ }
+
+ $this->filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Tell if the given entry can be accepted in the constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param mixed $id Whatever.
+ * @return bool
+ */
+ public static function constructor_accepts( $id ) {
+ if ( $id instanceof DataInterface ) {
+ return true;
+ }
+
+ $data_class = str_replace( '\\Optimization\\Process\\', '\\Optimization\\Data\\', get_called_class() );
+ $data_class = '\\' . ltrim( $data_class, '\\' );
+
+ return $data_class::constructor_accepts( $id );
+ }
+
+ /**
+ * Get the data instance.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return DataInterface|false
+ */
+ public function get_data() {
+ return $this->data;
+ }
+
+ /**
+ * Get the media instance.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return MediaInterface|false
+ */
+ public function get_media() {
+ if ( ! $this->get_data() ) {
+ return false;
+ }
+
+ return $this->get_data()->get_media();
+ }
+
+ /**
+ * Get the File instance of the original file.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return File|false
+ */
+ public function get_original_file() {
+ if ( isset( $this->file ) ) {
+ return $this->file;
+ }
+
+ $this->file = false;
+
+ if ( $this->get_media() ) {
+ $this->file = new File( $this->get_media()->get_raw_original_path() );
+ }
+
+ return $this->file;
+ }
+
+ /**
+ * Get the File instance of the full size file.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return File|false
+ */
+ public function get_fullsize_file() {
+ if ( isset( $this->file ) ) {
+ return $this->file;
+ }
+
+ $this->file = false;
+
+ if ( $this->get_media() ) {
+ $this->file = new File( $this->get_media()->get_raw_fullsize_path() );
+ }
+
+ return $this->file;
+ }
+
+ /**
+ * Tell if the current media is valid.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_valid() {
+ return $this->get_media() && $this->get_media()->is_valid();
+ }
+
+ /**
+ * Tell if the current user is allowed to operate Imagify in this context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $describer Capacity describer. See \Imagify\Context\ContextInterface->get_capacity() for possible values. Can also be a "real" user capacity.
+ * @return bool
+ */
+ public function current_user_can( $describer ) {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ $media = $this->get_media();
+
+ return $media->get_context_instance()->current_user_can( $describer, $media->get_id() );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Optimize a media files.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @param array $args An array of optionnal arguments.
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize( $optimization_level = null, $args = [] ) {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ $data = $this->get_data();
+
+ if ( $data->is_optimized() ) {
+ return new \WP_Error( 'optimized', __( 'This media has already been optimized by Imagify.', 'imagify' ) );
+ }
+
+ if ( $data->is_already_optimized() && $this->has_webp() ) {
+ // If already optimized but has webp, delete webp versions and optimization data.
+ $data->delete_optimization_data();
+ $deleted = $this->delete_webp_files();
+
+ if ( is_wp_error( $deleted ) ) {
+ return new \WP_Error( 'webp_not_deleted', __( 'Previous webp files could not be deleted.', 'imagify' ) );
+ }
+ }
+
+ $sizes = $media->get_media_files();
+ $args = is_array( $args ) ? $args : [];
+
+ $args['hook_suffix'] = 'optimize_media';
+
+ // Optimize.
+ return $this->optimize_sizes( array_keys( $sizes ), $optimization_level, $args );
+ }
+
+ /**
+ * Re-optimize a media files with a different level.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @param array $args An array of optionnal arguments.
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function reoptimize( $optimization_level = null, $args = [] ) {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ $data = $this->get_data();
+
+ if ( ! $data->get_optimization_status() ) {
+ return new \WP_Error( 'not_processed_yet', __( 'This media has not been processed yet.', 'imagify' ) );
+ }
+
+ $optimization_level = $this->sanitize_optimization_level( $optimization_level );
+
+ if ( $data->get_optimization_level() === $optimization_level ) {
+ return new \WP_Error( 'identical_optimization_level', __( 'This media is already optimized with this level.', 'imagify' ) );
+ }
+
+ $this->restore();
+
+ $sizes = $media->get_media_files();
+ $args = is_array( $args ) ? $args : [];
+
+ $args['hook_suffix'] = 'reoptimize_media';
+
+ // Optimize.
+ return $this->optimize_sizes( array_keys( $sizes ), $optimization_level, $args );
+ }
+
+ /**
+ * Optimize several file sizes by pushing tasks into the queue.
+ *
+ * @since 1.9
+ * @access public
+ * @see MediaOptimization->task_before()
+ * @see MediaOptimization->task_after()
+ * @author Grégory Viguier
+ *
+ * @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @param array $args {
+ * An array of optionnal arguments.
+ *
+ * @type string $hook_suffix Suffix used to trigger hooks before and after optimization.
+ * }
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize_sizes( $sizes, $optimization_level = null, $args = [] ) {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ if ( ! $sizes ) {
+ return new \WP_Error( 'no_sizes', __( 'No sizes given to be optimized.', 'imagify' ) );
+ }
+
+ if ( empty( $args['locked'] ) ) {
+ if ( $this->is_locked() ) {
+ return new \WP_Error( 'media_locked', __( 'This media is already being processed.', 'imagify' ) );
+ }
+
+ $this->lock();
+ }
+
+ if ( $media->is_image() ) {
+ if ( $this->get_option( 'convert_to_webp' ) ) {
+ // Add webp convertion.
+ $files = $media->get_media_files();
+
+ foreach ( $sizes as $size_name ) {
+ if ( empty( $files[ $size_name ] ) ) {
+ continue;
+ }
+ if ( 'image/webp' === $files[ $size_name ]['mime-type'] ) {
+ continue;
+ }
+ if ( in_array( $size_name . static::WEBP_SUFFIX, $sizes, true ) ) {
+ continue;
+ }
+
+ array_unshift( $sizes, $size_name . static::WEBP_SUFFIX );
+ }
+ }
+
+ if ( ! $media->get_context_instance()->can_backup() && ! $media->get_backup_path() && ! $this->get_data()->get_size_data( 'full', 'success' ) ) {
+ /**
+ * Backup is NOT activated, and a backup file does NOT exist yet, and the full size is NOT optimized yet.
+ * Webp conversion needs a backup file, even a temporary one: weâll create one.
+ */
+ $webp = false;
+
+ foreach ( $sizes as $size_name ) {
+ if ( $this->is_size_webp( $size_name ) ) {
+ $webp = true;
+ break;
+ }
+ }
+
+ if ( $webp ) {
+ // We have at least one webp conversion to do: create a temporary backup.
+ $backuped = $this->get_original_file()->backup( $media->get_raw_backup_path() );
+
+ if ( $backuped ) {
+ // See \Imagify\Job\MediaOptimization->delete_backup().
+ $args['delete_backup'] = true;
+ }
+ }
+ }
+ }
+
+ $sizes = array_unique( $sizes );
+ $optimization_level = $this->sanitize_optimization_level( $optimization_level );
+
+ /**
+ * Filter the data sent to the optimization process.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $new_args Additional data to send to the optimization process.
+ * @param array $args Current data sent to the process.
+ * @param ProcessInterface $process The current optimization process.
+ * @param array $sizes Sizes being processed.
+ * @param int $optimization_level Optimization level.
+ */
+ $new_args = apply_filters( 'imagify_optimize_sizes_args', [], $args, $this, $sizes, $optimization_level );
+
+ if ( $new_args && is_array( $new_args ) ) {
+ $args = array_merge( $new_args, $args );
+ }
+
+ /**
+ * Push the item to the queue, save the queue in the DB, empty the queue.
+ * A "batch" is then created in the DB with this unique item, it is then free to loop through its steps (files) without another item interfering (each media optimization has its own dedicated batch/queue).
+ */
+ MediaOptimization::get_instance()->push_to_queue( [
+ 'id' => $media->get_id(),
+ 'sizes' => $sizes,
+ 'optimization_level' => $optimization_level,
+ 'process_class' => get_class( $this ),
+ 'data' => $args,
+ ] )->save();
+
+ return true;
+ }
+
+ /**
+ * Optimize one file with Imagify directly.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The media size.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return array|\WP_Error Optimized image data. A \WP_Error object on error.
+ */
+ public function optimize_size( $size, $optimization_level = null ) {
+ if ( ! $this->is_valid() ) { // Bail out.
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+ $sizes = $media->get_media_files();
+ $thumb_size = $size;
+ $webp = $this->is_size_webp( $size );
+ $path_is_temp = false;
+
+ if ( $webp ) {
+ // We'll make sure the file is an image later.
+ $thumb_size = $webp; // Contains the name of the non-webp size.
+ $webp = true;
+ }
+
+ if ( empty( $sizes[ $thumb_size ]['path'] ) ) { // Bail out.
+ // This size is not in our list.
+ return new \WP_Error(
+ 'unknown_size',
+ sprintf(
+ /* translators: %s is a size name. */
+ __( 'The size %s is unknown.', 'imagify' ),
+ '' . esc_html( $thumb_size ) . ''
+ )
+ );
+ }
+
+ if ( $this->get_data()->get_size_data( $size, 'success' ) ) { // Bail out.
+ // This size is already optimized with Imagify, and must not be optimized again.
+ if ( $webp ) {
+ return new \WP_Error(
+ 'size_is_successfully_optimized',
+ sprintf(
+ /* translators: %s is a size name. */
+ __( 'The webp format for the size %s already exists.', 'imagify' ),
+ '' . esc_html( $thumb_size ) . ''
+ )
+ );
+ } else {
+ return new \WP_Error(
+ 'size_is_successfully_optimized',
+ sprintf(
+ /* translators: %s is a size name. */
+ __( 'The size %s is already optimized by Imagify.', 'imagify' ),
+ '' . esc_html( $thumb_size ) . ''
+ )
+ );
+ }
+ }
+
+ /**
+ * Starting from here, errors will be stored in the optimization data of the size.
+ */
+ $path = $sizes[ $thumb_size ]['path'];
+
+ $optimization_level = $this->sanitize_optimization_level( $optimization_level );
+
+ if ( $webp && $this->get_data()->get_size_data( $thumb_size, 'success' ) ) {
+ // We want a webp version but the source file is already optimized by Imagify.
+ $result = $this->create_temporary_copy( $thumb_size, $sizes );
+
+ if ( ! $result ) { // Bail out.
+ // Could not create a copy of the non-webp version.
+ $response = new \WP_Error(
+ 'non_webp_copy_failed',
+ sprintf(
+ /* translators: %s is a size name. */
+ __( 'Could not create an unoptimized copy of the size %s.', 'imagify' ),
+ '' . esc_html( $thumb_size ) . ''
+ )
+ );
+
+ $this->update_size_optimization_data( $response, $size, $optimization_level );
+
+ return $response;
+ }
+
+ /**
+ * $path now targets a temporary file.
+ */
+ $path = $this->get_temporary_copy_path( $thumb_size, $sizes );
+ $path_is_temp = true;
+ }
+
+ $file = new File( $path ); // Original file or temporary copy.
+
+ if ( ! $file->is_supported( $media->get_allowed_mime_types() ) ) { // Bail out.
+ // This file type is not supported.
+ $extension = $file->get_extension();
+
+ if ( '' === $extension ) {
+ $response = new \WP_Error(
+ 'no_extension',
+ __( 'With no extension, this file cannot be optimized.', 'imagify' )
+ );
+ } else {
+ $response = new \WP_Error(
+ 'extension_not_supported',
+ sprintf(
+ /* translators: %s is a file extension. */
+ __( '%s cannot be optimized.', 'imagify' ),
+ '' . esc_html( strtolower( $extension ) ) . ''
+ )
+ );
+ }
+
+ if ( $path_is_temp ) {
+ $this->filesystem->delete( $path );
+ }
+
+ $this->update_size_optimization_data( $response, $size, $optimization_level );
+
+ return $response;
+ }
+
+ if ( $webp && ! $file->is_image() ) { // Bail out.
+ if ( $path_is_temp ) {
+ $this->filesystem->delete( $path );
+ }
+
+ $response = new \WP_Error(
+ 'no_webp',
+ __( 'This file is not an image and cannot be converted to webp format.', 'imagify' )
+ );
+
+ $this->update_size_optimization_data( $response, $size, $optimization_level );
+
+ return $response;
+ }
+
+ $is_disabled = ! empty( $sizes[ $thumb_size ]['disabled'] );
+
+ /**
+ * Fires before optimizing a file.
+ * Return a \WP_Error object to prevent the optimization.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param null|\WP_Error $response Null by default. Return a \WP_Error object to prevent optimization.
+ * @param ProcessInterface $process The optimization process instance.
+ * @param File $file The file instance. If $webp is true, $file references the non-webp file.
+ * @param string $thumb_size The media size.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @param bool $webp The image will be converted to webp.
+ * @param bool $is_disabled Tell if this size is disabled from optimization.
+ */
+ $response = apply_filters( 'imagify_before_optimize_size', null, $this, $file, $thumb_size, $optimization_level, $webp, $is_disabled );
+
+ if ( ! is_wp_error( $response ) ) {
+ if ( $is_disabled ) {
+ // This size must not be optimized.
+ $response = new \WP_Error(
+ 'unauthorized_size',
+ sprintf(
+ /* translators: %s is a size name. */
+ __( 'The size %s is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
+ '' . esc_html( $thumb_size ) . ''
+ )
+ );
+ } elseif ( ! $this->filesystem->exists( $file->get_path() ) ) {
+ $response = new \WP_Error(
+ 'file_not_exists',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The file %s does not seem to exist.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $file->get_path() ) ) . ''
+ )
+ );
+ } elseif ( $webp && ! $this->can_create_webp_version( $file->get_path() ) ) {
+ $response = new \WP_Error(
+ 'is_animated_gif',
+ __( 'This file is an animated gif: since Imagify does not support animated webp, webp creation for animated gif is disabled.', 'imagify' )
+ );
+ } elseif ( ! $this->filesystem->is_writable( $file->get_path() ) ) {
+ $response = new \WP_Error(
+ 'file_not_writable',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The file %s does not seem to be writable.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $file->get_path() ) ) . ''
+ )
+ );
+ } else {
+ // Maybe resize the file.
+ $response = $this->maybe_resize( $thumb_size, $file );
+
+ if ( ! is_wp_error( $response ) ) {
+ // Resizing succeeded: optimize the file.
+ $response = $file->optimize( [
+ 'backup' => ! $response['backuped'] && $this->can_backup( $size ),
+ 'backup_path' => $media->get_raw_backup_path(),
+ 'backup_source' => 'full' === $thumb_size ? $media->get_original_path() : null,
+ 'optimization_level' => $optimization_level,
+ 'convert' => $webp ? 'webp' : '',
+ 'keep_exif' => $this->can_keep_exif( $size ),
+ 'context' => $media->get_context(),
+ 'original_size' => $response['file_size'],
+ ] );
+
+ $response = $this->compare_webp_file_size( [
+ 'response' => $response,
+ 'file' => $file,
+ 'is_webp' => $webp,
+ 'non_webp_thumb_size' => $thumb_size,
+ 'non_webp_file_path' => $sizes[ $thumb_size ]['path'], // Don't use $path nor $file->get_path(), it may return the path to a temporary file.
+ 'optimization_level' => $optimization_level,
+ ] );
+ }
+ }
+ }
+
+ $data = $this->update_size_optimization_data( $response, $size, $optimization_level );
+
+ /**
+ * Fires after optimizing a file.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process instance.
+ * @param File $file The file instance.
+ * @param string $thumb_size The media size.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @param bool $webp The image was supposed to be converted to webp.
+ * @param bool $is_disabled Tell if this size is disabled from optimization.
+ */
+ do_action( 'imagify_after_optimize_size', $this, $file, $thumb_size, $optimization_level, $webp, $is_disabled );
+
+ if ( ! $path_is_temp ) {
+ return $data;
+ }
+
+ // Delete the temporary copy.
+ $this->filesystem->delete( $path );
+
+ if ( is_wp_error( $response ) ) {
+ return $data;
+ }
+
+ // Rename the optimized file.
+ $destination_path = str_replace( static::TMP_SUFFIX . '.', '.', $file->get_path() );
+
+ $this->filesystem->move( $file->get_path(), $destination_path, true );
+
+ return $data;
+ }
+
+ /**
+ * Compare the file size of a file and its webp version: if the webp version is heavier than the non-webp file, delete it.
+ *
+ * @since 1.9.4
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $args {
+ * A list of mandatory arguments.
+ *
+ * @type \sdtClass|\WP_Error $response Optimized image data. A \WP_Error object on error.
+ * @type File $file The File instance of the file currently being optimized.
+ * @type bool $is_webp Tell if we're requesting a webp file.
+ * @type string $non_webp_thumb_size Name of the corresponding non-webp thumbnail size. If we're not creating a webp file, this corresponds to the current thumbnail size.
+ * @type string $non_webp_file_path Path to the corresponding non-webp file. If we're not creating a webp file, this corresponds to the current file path.
+ * @type string $optimization_level The optimization level.
+ * }
+ * @return \sdtClass|\WP_Error Optimized image data. A \WP_Error object on error.
+ */
+ protected function compare_webp_file_size( $args ) {
+ static $keep_large_webp;
+
+ if ( ! isset( $keep_large_webp ) ) {
+ /**
+ * Allow to not store webp images that are larger than their non-webp version.
+ *
+ * @since 1.9.4
+ * @author Grégory Viguier
+ *
+ * @param bool $keep_large_webp Set to false if you prefer your visitors over your Pagespeed score. Default value is true.
+ */
+ $keep_large_webp = apply_filters( 'imagify_keep_large_webp', true );
+ }
+
+ if ( $keep_large_webp || is_wp_error( $args['response'] ) || ! $args['file']->is_image() ) {
+ return $args['response'];
+ }
+
+ // Optimization succeeded.
+ if ( $args['is_webp'] ) {
+ /**
+ * We just created a webp version:
+ * Check if it is lighter than the (maybe optimized) non-webp file.
+ */
+ $data = $this->get_data()->get_size_data( $args['non_webp_thumb_size'] );
+
+ if ( ! $data ) {
+ // We havenât tried to optimize the non-webp size yet.
+ return $args['response'];
+ }
+
+ if ( ! empty( $data['optimized_size'] ) ) {
+ // The non-webp size is optimized, we know the file size.
+ $non_webp_file_size = $data['optimized_size'];
+ } else {
+ // The non-webp size is "already optimized" or "error": grab the file size directly from the file.
+ $non_webp_file_size = $this->filesystem->size( $args['non_webp_file_path'] );
+ }
+
+ if ( ! $non_webp_file_size || $non_webp_file_size > $args['response']->new_size ) {
+ // The new webp file is lighter.
+ return $args['response'];
+ }
+
+ // The new webp file is heavier than the non-webp file: delete it and return an error.
+ $this->filesystem->delete( $args['file']->get_path() );
+
+ return new \WP_Error(
+ 'webp_heavy',
+ sprintf(
+ /* translators: %s is a size name. */
+ __( 'The webp version of the size %s is heavier than its non-webp version.', 'imagify' ),
+ '' . esc_html( $args['non_webp_thumb_size'] ) . ''
+ )
+ );
+ }
+
+ /**
+ * We just created a non-webp version:
+ * Check if its webp version file is lighter than this one.
+ */
+ $webp_size = $args['non_webp_thumb_size'] . static::WEBP_SUFFIX;
+ $webp_file_size = $this->get_data()->get_size_data( $webp_size, 'optimized_size' );
+
+ if ( ! $webp_file_size || $webp_file_size < $args['response']->new_size ) {
+ // The webp file is lighter than this one.
+ return $args['response'];
+ }
+
+ // The new optimized file is lighter than the webp file: delete the webp file and store an error.
+ $webp_path = $args['file']->get_path_to_webp();
+
+ if ( $webp_path && $this->filesystem->is_writable( $webp_path ) ) {
+ $this->filesystem->delete( $webp_path );
+ }
+
+ $webp_response = new \WP_Error(
+ 'webp_heavy',
+ sprintf(
+ /* translators: %s is a size name. */
+ __( 'The webp version of the size %s is heavier than its non-webp version.', 'imagify' ),
+ '' . esc_html( $args['non_webp_thumb_size'] ) . ''
+ )
+ );
+
+ $this->update_size_optimization_data( $webp_response, $webp_size, $args['optimization_level'] );
+
+ return $args['response'];
+ }
+
+ /**
+ * Restore the media files from the backup file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True on success. A \WP_Error instance on failure.
+ */
+ public function restore() {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ if ( ! $media->has_backup() ) {
+ return new \WP_Error( 'no_backup', __( 'This media has no backup file.', 'imagify' ) );
+ }
+
+ if ( $this->is_locked() ) {
+ return new \WP_Error( 'media_locked', __( 'This media is already being processed.', 'imagify' ) );
+ }
+
+ $this->lock( 'restoring' );
+
+ $backup_path = $media->get_backup_path();
+ $original_path = $media->get_raw_original_path();
+
+ if ( $backup_path === $original_path ) {
+ // Uh?!
+ $this->unlock();
+ return new \WP_Error( 'same_path', __( 'Image path and backup path are identical.', 'imagify' ) );
+ }
+
+ $dest_dir = $this->filesystem->dir_path( $original_path );
+
+ if ( ! $this->filesystem->exists( $dest_dir ) ) {
+ $this->filesystem->make_dir( $dest_dir );
+ }
+
+ $dest_file_is_writable = ! $this->filesystem->exists( $original_path ) || $this->filesystem->is_writable( $original_path );
+
+ if ( ! $dest_file_is_writable || ! $this->filesystem->is_writable( $dest_dir ) ) {
+ $this->unlock();
+ return new \WP_Error( 'destination_not_writable', __( 'The image to replace is not writable.', 'imagify' ) );
+ }
+
+ // Get some data before doing anything.
+ $data = $this->get_data()->get_optimization_data();
+ $files = $media->get_media_files();
+
+ /**
+ * Fires before restoring a media.
+ * Return a \WP_Error object to prevent the restoration.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param null|\WP_Error $response Null by default. Return a \WP_Error object to prevent optimization.
+ * @param ProcessInterface $process Instance of this process.
+ */
+ $response = apply_filters( 'imagify_before_restore_media', null, $this );
+
+ if ( ! is_wp_error( $response ) ) {
+ // Create the original image from the backup.
+ $response = $this->filesystem->copy( $backup_path, $original_path, true );
+
+ if ( ! $response ) {
+ // Failure.
+ $response = new \WP_Error( 'copy_failed', __( 'The backup file could not be copied over the optimized one.', 'imagify' ) );
+ } else {
+ // Backup successfully copied.
+ $this->filesystem->chmod_file( $original_path );
+
+ // Remove old optimization data.
+ $this->get_data()->delete_optimization_data();
+
+ if ( $media->is_image() ) {
+ // Restore the original dimensions in the database.
+ $media->update_dimensions();
+
+ // Delete the webp version.
+ $this->delete_webp_file( $original_path );
+
+ // Restore the thumbnails.
+ $response = $this->restore_thumbnails();
+ }
+ }
+ }
+
+ /**
+ * Fires after restoring a media.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process Instance of this process.
+ * @param bool|WP_Error $response The result of the operation: true on success, a WP_Error object on failure.
+ * @param array $files The list of files, before restoring them.
+ * @param array $data The optimization data, before deleting it.
+ */
+ do_action( 'imagify_after_restore_media', $this, $response, $files, $data );
+
+ $this->unlock();
+
+ return $response;
+ }
+
+ /**
+ * Restore the thumbnails.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True on success. A \WP_Error instance on failure.
+ */
+ protected function restore_thumbnails() {
+ $media = $this->get_media();
+
+ /**
+ * Delete the webp versions.
+ * If the full size file and the original file are not the same, the full size is considered like a thumbnail.
+ * In that case we must also delete the webp file associated to the full size.
+ */
+ $keep_full_webp = $media->get_raw_original_path() === $media->get_raw_fullsize_path();
+ $this->delete_webp_files( $keep_full_webp );
+
+ // Generate new thumbnails.
+ return $media->generate_thumbnails();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BACKUP FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Delete the backup file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_backup() {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ $backup_path = $this->get_media()->get_backup_path();
+
+ if ( $backup_path ) {
+ $this->filesystem->delete( $backup_path );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TEMPORARY COPY OF A SIZE FILE =========================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * If we need to create a webp version, we must create it from an unoptimized image.
+ * The full size is always optimized before the webp version creation, and in some cases itâs the same for the thumbnails.
+ * Then we use the backup file to create temporary files.
+ */
+
+ /**
+ * Create a temporary copy of a size file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $size The image size name.
+ * @param array $sizes A list of thumbnail sizes being optimized.
+ * @return bool True if the file exists/is created. False on failure.
+ */
+ protected function create_temporary_copy( $size, $sizes = null ) {
+ $media = $this->get_media();
+
+ if ( ! isset( $sizes ) ) {
+ $sizes = $media->get_media_files();
+ }
+
+ if ( empty( $sizes[ $size ] ) ) {
+ // What?
+ return false;
+ }
+
+ $tmp_path = $this->get_temporary_copy_path( $size, $sizes );
+
+ if ( $tmp_path && $this->filesystem->exists( $tmp_path ) ) {
+ // The temporary file already exists.
+ return true;
+ }
+
+ $tmp_file = new File( $tmp_path );
+
+ if ( ! $tmp_file->is_image() ) {
+ // The file is not an image.
+ return false;
+ }
+
+ if ( ! $tmp_file->is_supported( $media->get_allowed_mime_types() ) ) {
+ // The file is not supported.
+ return false;
+ }
+
+ /**
+ * Use the backup file as source.
+ */
+ $backup_path = $media->get_backup_path();
+
+ if ( ! $backup_path ) {
+ // No backup, no hope for you.
+ return false;
+ }
+
+ /**
+ * In all cases we must make a copy of the backup file, and not use the backup directly:
+ * sometimes the backup image does not have a valid file extension (yes Iâm looking at you NextGEN Gallery).
+ */
+ $copied = $this->filesystem->copy( $backup_path, $tmp_path, true );
+
+ if ( ! $copied ) {
+ return false;
+ }
+
+ if ( 'full' === $size ) {
+ /**
+ * We create a copy of the backup to be able to create a webp version from it.
+ * That means the optimization process will resize the file if needed, so there is nothing more to do here.
+ */
+ return true;
+ }
+
+ // We need to create a thumbnail from it.
+ $size_data = $sizes[ $size ];
+ $context_sizes = $media->get_context_instance()->get_thumbnail_sizes();
+
+ if ( ! empty( $context_sizes[ $size ] ) ) {
+ // Not a dynamic size, yay!
+ $size_data = array_merge( $size_data, $context_sizes[ $size ] );
+ }
+
+ if ( empty( $size_data['path'] ) ) {
+ // Should not happen.
+ return false;
+ }
+
+ if ( ! isset( $size_data['crop'] ) ) {
+ /**
+ * In case of a dynamic thumbnail we donât know if the image must be croped or resized.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $crop True to crop the thumbnail, false to resize. Null by default.
+ * @param string $size Name of the thumbnail size.
+ * @param array $size_data Data of the thumbnail being processed. Contains at least 'width', 'height', and 'path'.
+ * @param MediaInterface $media The MediaInterface instance corresponding to the image being processed.
+ */
+ $crop = apply_filters( 'imagify_crop_thumbnail', null, $size, $size_data, $media );
+
+ if ( null !== $crop ) {
+ $size_data['crop'] = (bool) $crop;
+ }
+ }
+
+ if ( ! isset( $size_data['crop'] ) ) {
+ // We don't have the 'crop' data in that case: letâs try to guess it.
+ if ( ! $size_data['height'] || ! $size_data['width'] ) {
+ // One of the size dimensions is 0, that means crop is probably disabled.
+ $size_data['crop'] = false;
+ } else {
+ if ( ! $this->filesystem->exists( $size_data['path'] ) ) {
+ // Screwed.
+ return false;
+ }
+
+ $thumb_dimensions = $this->filesystem->get_image_size( $size_data['path'] );
+
+ if ( ! $thumb_dimensions || ! $thumb_dimensions['width'] || ! $thumb_dimensions['height'] ) {
+ // ( ; Ð ; )
+ return false;
+ }
+
+ // Compare dimensions.
+ $new_height = $thumb_dimensions['width'] * $size_data['height'] / $size_data['width'];
+ // If the difference is > to 1px, let's assume that crop is enabled.
+ $size_data['crop'] = abs( $thumb_dimensions['height'] - $new_height ) > 1;
+ }
+ }
+
+ $resized = $tmp_file->create_thumbnail( [
+ 'path' => $tmp_path,
+ 'width' => $size_data['width'],
+ 'height' => $size_data['height'],
+ 'crop' => $size_data['crop'],
+ 'adjust_filename' => false,
+ ] );
+
+ if ( is_wp_error( $resized ) ) {
+ return false;
+ }
+
+ // Make sure the new file has the expected name.
+ $new_tmp_path = $this->filesystem->dir_path( $tmp_path ) . $resized['file'];
+
+ if ( $new_tmp_path === $tmp_path ) {
+ return true;
+ }
+
+ return $this->filesystem->move( $new_tmp_path, $tmp_path, true );
+ }
+
+ /**
+ * Get the path to a temporary copy of a size file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $size The image size name.
+ * @param array $sizes A list of thumbnail sizes being optimized.
+ * @return string|bool An image path. False on failure.
+ */
+ protected function get_temporary_copy_path( $size, $sizes = null ) {
+ if ( 'full' === $size ) {
+ $path = $this->get_media()->get_raw_fullsize_path();
+ } else {
+ if ( ! isset( $sizes ) ) {
+ $sizes = $this->get_media()->get_media_files();
+ }
+
+ $path = ! empty( $sizes[ $size ]['path'] ) ? $sizes[ $size ]['path'] : false;
+ }
+
+ if ( ! $path ) {
+ return false;
+ }
+
+ $info = $this->filesystem->path_info( $path );
+
+ if ( ! $info['file_base'] ) {
+ return false;
+ }
+
+ return $info['dir_path'] . $info['file_base'] . static::TMP_SUFFIX . '.' . $info['extension'];
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** RESIZE FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Maybe resize an image.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @param File $file A File instance.
+ * @return array|WP_Error A \WP_Error instance on failure, an array on success as follow: {
+ * @type bool $resized True when the image has been resized.
+ * @type bool $backuped True when the image has been backuped.
+ * @type int $file_size The file size in bytes.
+ * }
+ */
+ public function maybe_resize( $size, $file ) {
+ if ( ! $this->can_resize( $size, $file ) ) {
+ // This file should not be resized.
+ return [
+ 'resized' => false,
+ 'backuped' => false,
+ 'file_size' => 0,
+ ];
+ }
+
+ $media = $this->get_media();
+ $dimensions = $media->get_dimensions();
+
+ if ( ! $dimensions['width'] ) {
+ // The dimensions don't seem to be in the database anymore: try to get them directly from the file.
+ $dimensions = $file->get_dimensions();
+ }
+
+ if ( ! $dimensions['width'] ) {
+ // Could not get the image dimensions.
+ return new \WP_Error(
+ 'no_dimensions',
+ sprintf(
+ /* translators: %s is an error message. */
+ __( 'Resizing failed: %s', 'imagify' ),
+ __( 'Imagify could not get the image dimensions.', 'imagify' )
+ )
+ );
+ }
+
+ $resize_width = $media->get_context_instance()->get_resizing_threshold();
+
+ if ( $resize_width >= $dimensions['width'] ) {
+ // No need to resize.
+ return [
+ 'resized' => false,
+ 'backuped' => false,
+ 'file_size' => 0,
+ ];
+ }
+
+ $resized_path = $file->resize( $dimensions, $resize_width );
+
+ if ( is_wp_error( $resized_path ) ) {
+ // The resizement failed.
+ return new \WP_Error(
+ 'resize_failure',
+ sprintf(
+ /* translators: %s is an error message. */
+ __( 'Resizing failed: %s', 'imagify' ),
+ $resized_path->get_error_message()
+ )
+ );
+ }
+
+ if ( $this->can_backup( $size ) ) {
+ $source = 'full' === $size ? $media->get_original_path() : null;
+ $backuped = $file->backup( $media->get_raw_backup_path(), $source );
+
+ if ( is_wp_error( $backuped ) ) {
+ // The backup failed.
+ return new \WP_Error(
+ 'backup_failure',
+ sprintf(
+ /* translators: %s is an error message. */
+ __( 'Backup failed: %s', 'imagify' ),
+ $backuped->get_error_message()
+ )
+ );
+ }
+ } else {
+ $backuped = false;
+ }
+
+ $file_size = (int) $this->filesystem->size( $file->get_path() );
+ $resized = $this->filesystem->move( $resized_path, $file->get_path(), true );
+
+ if ( ! $resized ) {
+ // The resizement failed.
+ return new \WP_Error(
+ 'resize_move_failure',
+ __( 'The image could not be replaced by the resized one.', 'imagify' )
+ );
+ }
+
+ // Store the new dimensions.
+ $media->update_dimensions();
+
+ return [
+ 'resized' => true,
+ 'backuped' => $backuped,
+ 'file_size' => $file_size,
+ ];
+ }
+
+ /**
+ * Tell if a size should be resized.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @param File $file A File instance.
+ * @return bool
+ */
+ protected function can_resize( $size, $file ) {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( 'full' !== $size && 'full' . static::WEBP_SUFFIX !== $size ) {
+ // We resize only the main file and its webp version.
+ return false;
+ }
+
+ if ( ! $file->is_image() ) {
+ return false;
+ }
+
+ return $this->get_media()->get_context_instance()->can_resize();
+ }
+
+ /**
+ * Tell if a size should be backuped.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @return bool
+ */
+ protected function can_backup( $size ) {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( 'full' !== $size ) {
+ // We backup only the main file.
+ return false;
+ }
+
+ return $this->get_media()->get_context_instance()->can_backup();
+ }
+
+ /**
+ * Tell if a size should keep exif.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @return bool
+ */
+ protected function can_keep_exif( $size ) {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( 'full' !== $size && 'full' . static::WEBP_SUFFIX !== $size ) {
+ // We keep exif only on the main file and its webp version.
+ return false;
+ }
+
+ return $this->get_media()->get_context_instance()->can_keep_exif();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WEBP ==================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Generate webp images if they are missing.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function generate_webp_versions() {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_image() ) {
+ return new \WP_Error( 'no_webp', __( 'This media is not an image and cannot be converted to webp format.', 'imagify' ) );
+ }
+
+ if ( ! $media->has_backup() ) {
+ return new \WP_Error( 'no_backup', __( 'This media has no backup file.', 'imagify' ) );
+ }
+
+ $data = $this->get_data();
+
+ if ( ! $data->is_optimized() && ! $data->is_already_optimized() ) {
+ return new \WP_Error( 'not_optimized', __( 'This media has not been optimized by Imagify yet.', 'imagify' ) );
+ }
+
+ if ( $this->has_webp() ) {
+ return new \WP_Error( 'has_webp', __( 'This media already has webp versions.', 'imagify' ) );
+ }
+
+ $files = $media->get_media_files();
+ $sizes = [];
+ $args = [
+ 'hook_suffix' => 'generate_webp_versions',
+ ];
+
+ foreach ( $files as $size_name => $file ) {
+ if ( 'image/webp' !== $files[ $size_name ]['mime-type'] ) {
+ array_unshift( $sizes, $size_name . static::WEBP_SUFFIX );
+ }
+ }
+
+ if ( ! $sizes ) {
+ return new \WP_Error( 'no_sizes', __( 'This media does not have files that can be converted to webp format.', 'imagify' ) );
+ }
+
+ $optimization_level = $data->get_optimization_level();
+
+ // Optimize.
+ return $this->optimize_sizes( $sizes, $optimization_level, $args );
+ }
+
+ /**
+ * Delete the webp images.
+ * This doesn't delete the related optimization data.
+ *
+ * @since 1.9
+ * @since 1.9.6 Return WP_Error or true.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $keep_full Set to true to keep the full size.
+ * @return bool|\WP_Error True on success. A \WP_Error object on failure.
+ */
+ public function delete_webp_files( $keep_full = false ) {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_image() ) {
+ return new \WP_Error( 'media_not_an_image', __( 'This media is not an image.', 'imagify' ) );
+ }
+
+ $files = $media->get_media_files();
+
+ if ( $keep_full ) {
+ unset( $files['full'] );
+ }
+
+ if ( ! $files ) {
+ return true;
+ }
+
+ $error_count = 0;
+
+ foreach ( $files as $file ) {
+ if ( 0 === strpos( $file['mime-type'], 'image/' ) ) {
+ $deleted = $this->delete_webp_file( $file['path'] );
+
+ if ( is_wp_error( $deleted ) ) {
+ ++$error_count;
+ }
+ }
+ }
+
+ if ( $error_count ) {
+ return new \WP_Error(
+ 'files_not_deleted',
+ sprintf(
+ /* translators: %s is a formatted number, donât use %d. */
+ _n( '%s file could not be deleted.', '%s files could not be deleted.', $error_count, 'imagify' ),
+ number_format_i18n( $error_count )
+ )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Delete a webp image, given its non-webp version's path.
+ * This doesn't delete the related optimization data.
+ *
+ * @since 1.9
+ * @since 1.9.6 Return WP_Error or true.
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the non-webp file.
+ * @return bool|\WP_Error True on success. A \WP_Error object on failure.
+ */
+ protected function delete_webp_file( $file_path ) {
+ if ( ! $file_path ) {
+ return new \WP_Error( 'no_path', __( 'Path to non-webp file not provided.', 'imagify' ) );
+ }
+
+ $webp_file = new File( $file_path );
+ $webp_path = $webp_file->get_path_to_webp();
+
+ if ( ! $webp_path ) {
+ return new \WP_Error( 'no_webp_path', __( 'Could not get the path to the webp file.', 'imagify' ) );
+ }
+
+ if ( ! $this->filesystem->exists( $webp_path ) ) {
+ return true;
+ }
+
+ if ( ! $this->filesystem->is_writable( $webp_path ) ) {
+ return new \WP_Error(
+ 'file_not_writable',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The file %s does not seem to be writable.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $webp_path ) ) . ''
+ )
+ );
+ }
+
+ if ( ! $this->filesystem->is_file( $webp_path ) ) {
+ return new \WP_Error(
+ 'not_a_file',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'This does not seem to be a file: %s.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $webp_path ) ) . ''
+ )
+ );
+ }
+
+ $deleted = $this->filesystem->delete( $webp_path, false, 'f' );
+
+ if ( ! $deleted ) {
+ return new \WP_Error(
+ 'file_not_deleted',
+ sprintf(
+ /* translators: %s is a file path. */
+ __( 'The file %s could not be deleted.', 'imagify' ),
+ '' . esc_html( $this->filesystem->make_path_relative( $webp_path ) ) . ''
+ )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Tell if a thumbnail size is an "Imagify webp" size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size_name The size name.
+ * @return string|bool The unsuffixed name of the size if webp. False if not webp.
+ */
+ public function is_size_webp( $size_name ) {
+ static $suffix;
+
+ if ( ! isset( $suffix ) ) {
+ $suffix = preg_quote( static::WEBP_SUFFIX, '/' );
+ }
+
+ if ( preg_match( '/^(?.+)' . $suffix . '$/', $size_name, $matches ) ) {
+ return $matches['size'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Tell if the media has webp versions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_webp() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( ! $this->get_media()->is_image() ) {
+ return false;
+ }
+
+ $data = $this->get_data()->get_optimization_data();
+
+ if ( empty( $data['sizes'] ) ) {
+ return false;
+ }
+
+ $needle = static::WEBP_SUFFIX . '";a:4:{s:7:"success";b:1;';
+ $data = maybe_serialize( $data['sizes'] );
+
+ return is_string( $data ) && strpos( $data, $needle );
+ }
+
+ /**
+ * Tell if a webp version can be created for the given file.
+ * Make sure the file is an image before using this method.
+ *
+ * @since 1.9.5
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return bool
+ */
+ public function can_create_webp_version( $file_path ) {
+ if ( ! $file_path ) {
+ return false;
+ }
+
+ /**
+ * Tell if a webp version can be created for the given file.
+ * The file is an image.
+ *
+ * @since 1.9.5
+ * @author Grégory Viguier
+ *
+ * @param bool $can True to create a webp version, false otherwise. Null by default.
+ * @param string $file_path Path to the file.
+ */
+ $can = apply_filters( 'imagify_pre_can_create_webp_version', null, $file_path );
+
+ if ( isset( $can ) ) {
+ return (bool) $can;
+ }
+
+ $is_animated_gif = $this->filesystem->is_animated_gif( $file_path );
+
+ if ( is_bool( $is_animated_gif ) ) {
+ // Ok if itâs not an animated gif.
+ return ! $is_animated_gif;
+ }
+
+ // At this point $is_animated_gif is null, which means the file cannot be read (yet).
+ return true;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PROCESS STATUS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if a process is running for this media.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The action if locked ('optimizing' or 'restoring'). False if not locked.
+ */
+ public function is_locked() {
+ $name = $this->get_lock_name();
+
+ if ( ! $name ) {
+ return false;
+ }
+
+ $callback = $this->get_media()->get_context_instance()->is_network_wide() ? 'get_site_transient' : 'get_transient';
+ $action = call_user_func( $callback, $name );
+
+ if ( ! $action ) {
+ return false;
+ }
+
+ return $this->validate_lock_action( $action );
+ }
+
+ /**
+ * Set the running status to "running" for 10 minutes.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $action The action performed behind this lock: 'optimizing' or 'restoring'.
+ */
+ public function lock( $action = 'optimizing' ) {
+ $name = $this->get_lock_name();
+
+ if ( ! $name ) {
+ return;
+ }
+
+ $action = $this->validate_lock_action( $action );
+ $media = $this->get_media();
+ $callback = $media->get_context_instance()->is_network_wide() ? 'set_site_transient' : 'set_transient';
+
+ call_user_func( $callback, $name, $action, 10 * MINUTE_IN_SECONDS );
+ }
+
+ /**
+ * Unset the running status.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function unlock() {
+ $name = $this->get_lock_name();
+
+ if ( ! $name ) {
+ return false;
+ }
+
+ $callback = $this->get_media()->get_context_instance()->is_network_wide() ? 'delete_site_transient' : 'delete_transient';
+
+ call_user_func( $callback, $name );
+ }
+
+ /**
+ * Get the name of the transient that stores the lock status.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string|bool The name on success. False on failure.
+ */
+ protected function get_lock_name() {
+ $media = $this->get_media();
+
+ if ( ! $media ) {
+ return false;
+ }
+
+ /**
+ * Note that the site transient used by WP Background is named '*_process_lock'.
+ * That would give something like 'imagify_optimize_media_process_lock' for the optimization process, while here it would be 'imagify_wp_42_process_locked'.
+ */
+ return sprintf( static::LOCK_NAME, $media->get_context(), $media->get_id() );
+ }
+
+ /**
+ * Validate the lock action.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $action The action performed behind this lock: 'optimizing' or 'restoring'.
+ * @return string The valid action.
+ */
+ protected function validate_lock_action( $action ) {
+ switch ( $action ) {
+ case 'restore':
+ case 'restoring':
+ $action = 'restoring';
+ break;
+
+ default:
+ $action = 'optimizing';
+ }
+
+ return $action;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** DATA ==================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if a size already has optimization data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @return bool
+ */
+ public function size_has_optimization_data( $size ) {
+ $data = $this->get_data()->get_optimization_data();
+
+ return ! empty( $data['sizes'][ $size ] );
+ }
+
+ /**
+ * Update the optimization data for a size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param object $response The API response.
+ * @param string $size The size name.
+ * @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return array {
+ * The optimization data.
+ *
+ * @type int $level The optimization level.
+ * @type string $status The status: 'success', 'already_optimized', 'error'.
+ * @type bool $success True if successfully optimized. False on error or if already optimized.
+ * @type string $error An error message.
+ * @type int $original_size The weight of the file, before optimization.
+ * @type int $optimized_size The weight of the file, once optimized.
+ * }
+ */
+ public function update_size_optimization_data( $response, $size, $level ) {
+ $disabled = false;
+ $data = $this->data_format;
+
+ $data['level'] = is_numeric( $level ) ? (int) $level : $this->get_option( 'optimization_level' );
+
+ if ( is_wp_error( $response ) ) {
+ /**
+ * Error.
+ */
+ $disabled = 'unauthorized_size' === $response->get_error_code();
+
+ // Size data.
+ $data['success'] = false;
+ $data['error'] = $response->get_error_message();
+
+ // Status.
+ if ( false !== strpos( $data['error'], 'This image is already compressed' ) ) {
+ $data['status'] = 'already_optimized';
+ } else {
+ $data['status'] = 'error';
+ }
+ } else {
+ /**
+ * Success.
+ */
+ $response = (object) array_merge( [
+ 'original_size' => 0,
+ 'new_size' => 0,
+ 'percent' => 0,
+ ], (array) $response );
+
+ // Status.
+ $data['status'] = 'success';
+ $data['error'] = null;
+
+ // Size data.
+ $data['success'] = true;
+ $data['original_size'] = $response->original_size;
+ $data['optimized_size'] = $response->new_size;
+ }
+
+ $_unauthorized = $disabled ? '_unauthorized' : '';
+
+ /**
+ * Filter the optimization data.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $data {
+ * The optimization data.
+ *
+ * @type int $level The optimization level.
+ * @type string $status The status: 'success', 'already_optimized', 'error'.
+ * @type bool $success True if successfully optimized. False on error or if already optimized.
+ * @type string $error An error message.
+ * @type int $original_size The weight of the file, before optimization.
+ * @type int $optimized_size The weight of the file, once optimized.
+ * }
+ * @param object $response The API response.
+ * @param string $size The size name.
+ * @param int $level The optimization level.
+ * @param object $media_data The DataInterface instance of the media.
+ */
+ $data = (array) apply_filters( "imagify{$_unauthorized}_file_optimization_data", $data, $response, $size, $level, $this->get_data() );
+
+ // Store.
+ $this->get_data()->update_size_optimization_data( $size, $data );
+
+ return $data;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** VARIOUS TOOLS =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get a pluginâs option.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $option_name The option nme.
+ * @return mixed
+ */
+ protected function get_option( $option_name ) {
+ if ( isset( $this->options[ $option_name ] ) ) {
+ return $this->options[ $option_name ];
+ }
+
+ $this->options[ $option_name ] = get_imagify_option( $option_name );
+
+ return $this->options[ $option_name ];
+ }
+
+ /**
+ * Sanitize and validate an optimization level.
+ * If not provided (false, null), fallback to the level set in the plugin's settings.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param mixed $optimization_level The optimization level.
+ * @return int
+ */
+ protected function sanitize_optimization_level( $optimization_level ) {
+ if ( ! is_numeric( $optimization_level ) ) {
+ return $this->get_option( 'optimization_level' );
+ }
+
+ return \Imagify_Options::get_instance()->sanitize_and_validate( 'optimization_level', $optimization_level );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Process/CustomFolders.php b/wp-content/plugins/imagify/classes/Optimization/Process/CustomFolders.php
new file mode 100644
index 00000000..7e1ea6d5
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Process/CustomFolders.php
@@ -0,0 +1,103 @@
+is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ $data = $this->get_data();
+
+ if ( ! $data->is_optimized() ) {
+ return new \WP_Error( 'media_not_optimized', __( 'This media is not optimized yet.', 'imagify' ) );
+ }
+
+ if ( ! $media->has_backup() ) {
+ return new \WP_Error( 'no_backup', __( 'This file has no backup file.', 'imagify' ) );
+ }
+
+ if ( ! $media->is_image() ) {
+ return new \WP_Error( 'media_not_an_image', __( 'This media is not an image.', 'imagify' ) );
+ }
+
+ return [];
+ }
+
+ /**
+ * Optimize missing thumbnail sizes.
+ * Since this context has no thumbnails, this will always return a \WP_Error object.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize_missing_thumbnails() {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ if ( ! $this->get_media()->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ return new \WP_Error( 'no_sizes', __( 'No thumbnails seem to be missing.', 'imagify' ) );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Process/Noop.php b/wp-content/plugins/imagify/classes/Optimization/Process/Noop.php
new file mode 100644
index 00000000..bcbaf30e
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Process/Noop.php
@@ -0,0 +1,413 @@
+get_capacity() for possible values. Can also be a "real" user capacity.
+ * @return bool
+ */
+ public function current_user_can( $describer ) {
+ return false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Optimize a media files by pushing tasks into the queue.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize( $optimization_level = null ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ /**
+ * Re-optimize a media files with a different level.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function reoptimize( $optimization_level = null ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ /**
+ * Optimize several file sizes by pushing tasks into the queue.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize_sizes( $sizes, $optimization_level = null ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ /**
+ * Optimize one file with Imagify directly.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The media size.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return array|WP_Error The optimization data. A \WP_Error instance on failure.
+ */
+ public function optimize_size( $size, $optimization_level = null ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ /**
+ * Restore the media files from the backup file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True on success. A \WP_Error instance on failure.
+ */
+ public function restore() {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** MISSING THUMBNAILS ====================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the sizes for this media that have not get through optimization.
+ * No sizes are returned if the file is not optimized, has no backup, or is not an image.
+ * The 'full' size os never returned.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array|WP_Error {
+ * A WP_Error object on failure.
+ * An array of data for the thumbnail sizes on success.
+ * Size names are used as array keys.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * @type bool $crop True to crop, false to resize.
+ * @type string $name The size name.
+ * @type string $file The name the thumbnail "should" have.
+ * }
+ */
+ public function get_missing_sizes() {
+ return [];
+ }
+
+ /**
+ * Optimize missing thumbnail sizes.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize_missing_thumbnails() {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BACKUP FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Delete the backup file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_backup() {}
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** RESIZE FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Maybe resize an image.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @param File $file A File instance.
+ * @return array|WP_Error A \WP_Error instance on failure, an array on success as follow: {
+ * @type bool $resized True when the image has been resized.
+ * @type bool $backuped True when the image has been backuped.
+ * @type int $file_size The file size in bytes.
+ * }
+ */
+ public function maybe_resize( $size, $file ) {
+ return [
+ 'resized' => false,
+ 'backuped' => false,
+ 'file_size' => 0,
+ ];
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WEBP ==================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Generate webp images if they are missing.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function generate_webp_versions() {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ /**
+ * Delete the webp images.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_webp_files() {}
+
+ /**
+ * Tell if a thumbnail size is an "Imagify webp" size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size_name The size name.
+ * @return string|bool The unsuffixed name of the size if webp. False if not webp.
+ */
+ public function is_size_webp( $size_name ) {
+ return false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PROCESS STATUS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if a process is running for this media.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_locked() {
+ return false;
+ }
+
+ /**
+ * Set the running status to "running" for a period of time.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function lock() {}
+
+ /**
+ * Delete the running status.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function unlock() {}
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** DATA ==================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if a size already has optimization data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @return bool
+ */
+ public function size_has_optimization_data( $size ) {
+ return false;
+ }
+
+ /**
+ * Update the optimization data for a size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param object $response The API response.
+ * @param string $size The size name.
+ * @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return array {
+ * The optimization data.
+ *
+ * @type string $size The size name.
+ * @type int $level The optimization level.
+ * @type string $status The status: 'success', 'already_optimized', 'error'.
+ * @type bool $success True if successfully optimized. False on error or if already optimized.
+ * @type string $error An error message.
+ * @type int $original_size The weight of the file, before optimization.
+ * @type int $optimized_size The weight of the file, once optimized.
+ * }
+ */
+ public function update_size_optimization_data( $response, $size, $level ) {
+ return [
+ 'size' => 'noop',
+ 'level' => false,
+ 'status' => '',
+ 'success' => false,
+ 'error' => '',
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ ];
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Process/ProcessInterface.php b/wp-content/plugins/imagify/classes/Optimization/Process/ProcessInterface.php
new file mode 100644
index 00000000..252cc851
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Process/ProcessInterface.php
@@ -0,0 +1,362 @@
+get_capacity() for possible values. Can also be a "real" user capacity.
+ * @return bool
+ */
+ public function current_user_can( $describer );
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Optimize a media files by pushing tasks into the queue.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize( $optimization_level = null );
+
+ /**
+ * Re-optimize a media files with a different level.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function reoptimize( $optimization_level = null );
+
+ /**
+ * Optimize several file sizes by pushing tasks into the queue.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize_sizes( $sizes, $optimization_level = null );
+
+ /**
+ * Optimize one file with Imagify directly.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The media size.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return array|WP_Error The optimization data. A \WP_Error instance on failure.
+ */
+ public function optimize_size( $size, $optimization_level = null );
+
+ /**
+ * Restore the media files from the backup file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True on success. A \WP_Error instance on failure.
+ */
+ public function restore();
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** MISSING THUMBNAILS ====================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the sizes for this media that have not get through optimization.
+ * No sizes are returned if the file is not optimized, has no backup, or is not an image.
+ * The 'full' size os never returned.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array|WP_Error {
+ * A WP_Error object on failure.
+ * An array of data for the thumbnail sizes on success.
+ * Size names are used as array keys.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * @type bool $crop True to crop, false to resize.
+ * @type string $name The size name.
+ * @type string $file The name the thumbnail "should" have.
+ * }
+ */
+ public function get_missing_sizes();
+
+ /**
+ * Optimize missing thumbnail sizes.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize_missing_thumbnails();
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BACKUP FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Delete the backup file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_backup();
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** RESIZE FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Maybe resize an image.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @param File $file A File instance.
+ * @return array|WP_Error A \WP_Error instance on failure, an array on success as follow: {
+ * @type bool $resized True when the image has been resized.
+ * @type bool $backuped True when the image has been backuped.
+ * @type int $file_size The file size in bytes.
+ * }
+ */
+ public function maybe_resize( $size, $file );
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WEBP ==================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Generate webp images if they are missing.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function generate_webp_versions();
+
+ /**
+ * Delete the webp images.
+ * This doesn't delete the related optimization data.
+ *
+ * @since 1.9
+ * @since 1.9.6 Return WP_Error or true.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $keep_full Set to true to keep the full size.
+ * @return bool|\WP_Error True on success. A \WP_Error object on failure.
+ */
+ public function delete_webp_files( $keep_full = false );
+
+ /**
+ * Tell if a thumbnail size is an "Imagify webp" size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size_name The size name.
+ * @return string|bool The unsuffixed name of the size if webp. False if not webp.
+ */
+ public function is_size_webp( $size_name );
+
+ /**
+ * Tell if the media has webp versions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_webp();
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PROCESS STATUS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if a process is running for this media.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_locked();
+
+ /**
+ * Set the running status to "running" for a period of time.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function lock();
+
+ /**
+ * Delete the running status.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function unlock();
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** DATA ==================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if a size already has optimization data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @return bool
+ */
+ public function size_has_optimization_data( $size );
+
+ /**
+ * Update the optimization data for a size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param object $response The API response.
+ * @param string $size The size name.
+ * @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @return array {
+ * The optimization data.
+ *
+ * @type string $size The size name.
+ * @type int $level The optimization level.
+ * @type string $status The status: 'success', 'already_optimized', 'error'.
+ * @type bool $success True if successfully optimized. False on error or if already optimized.
+ * @type string $error An error message.
+ * @type int $original_size The weight of the file, before optimization.
+ * @type int $optimized_size The weight of the file, once optimized.
+ * }
+ */
+ public function update_size_optimization_data( $response, $size, $level );
+}
diff --git a/wp-content/plugins/imagify/classes/Optimization/Process/WP.php b/wp-content/plugins/imagify/classes/Optimization/Process/WP.php
new file mode 100644
index 00000000..74a2190f
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Optimization/Process/WP.php
@@ -0,0 +1,255 @@
+is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ $data = $this->get_data();
+
+ if ( ! $data->is_optimized() ) {
+ return new \WP_Error( 'media_not_optimized', __( 'This media is not optimized yet.', 'imagify' ) );
+ }
+
+ if ( ! $media->has_backup() ) {
+ return new \WP_Error( 'no_backup', __( 'This file has no backup file.', 'imagify' ) );
+ }
+
+ if ( ! $media->is_image() ) {
+ return new \WP_Error( 'media_not_an_image', __( 'This media is not an image.', 'imagify' ) );
+ }
+
+ // Compare registered sizes and optimized sizes.
+ $context_sizes = $media->get_context_instance()->get_thumbnail_sizes();
+ $optimized_sizes = $data->get_optimization_data();
+ $missing_sizes = array_diff_key( $context_sizes, $optimized_sizes['sizes'] );
+
+ if ( ! $missing_sizes ) {
+ // We have everything we need.
+ return [];
+ }
+
+ $media_sizes = $media->get_media_files();
+ $full_size = $media_sizes['full'];
+
+ if ( ! $full_size['path'] || ! $full_size['width'] || ! $full_size['height'] ) {
+ return [];
+ }
+
+ $file_name = $this->filesystem->path_info( $full_size['path'] );
+ $file_name = $file_name['file_base'] . '-{%suffix%}.' . $file_name['extension'];
+
+ // Test if the missing sizes are needed.
+ foreach ( $missing_sizes as $size_name => $size_data ) {
+ if ( $full_size['width'] === $size_data['width'] && $full_size['height'] === $size_data['height'] ) {
+ // Same dimensions as the full size.
+ unset( $missing_sizes[ $size_name ] );
+ continue;
+ }
+
+ if ( ! empty( $media_sizes[ $size_name ]['disabled'] ) ) {
+ // This size must not be optimized.
+ unset( $missing_sizes[ $size_name ] );
+ continue;
+ }
+
+ $resize_result = image_resize_dimensions( $full_size['width'], $full_size['height'], $size_data['width'], $size_data['height'], $size_data['crop'] );
+
+ if ( ! $resize_result ) {
+ // This thumbnail is not needed, it is smaller than this size.
+ unset( $missing_sizes[ $size_name ] );
+ continue;
+ }
+
+ // Provide what should be the file name.
+ list( , , , , $new_width, $new_height ) = $resize_result;
+ $missing_sizes[ $size_name ]['file'] = str_replace( '{%suffix%}', "{$new_width}x{$new_height}", $file_name );
+ }
+
+ return $missing_sizes;
+ }
+
+ /**
+ * Optimize missing thumbnail sizes.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize_missing_thumbnails() {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ if ( ! $this->get_media()->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ $missing_sizes = $this->get_missing_sizes();
+
+ if ( ! $missing_sizes ) {
+ return new \WP_Error( 'no_sizes', __( 'No thumbnails seem to be missing.', 'imagify' ) );
+ }
+
+ if ( is_wp_error( $missing_sizes ) ) {
+ return $missing_sizes;
+ }
+
+ if ( $this->is_locked() ) {
+ return new \WP_Error( 'media_locked', __( 'This media is already being processed.', 'imagify' ) );
+ }
+
+ $this->lock();
+
+ // Create the missing thumbnails.
+ $sizes = $this->create_missing_thumbnails( $missing_sizes );
+
+ if ( ! $sizes ) {
+ $this->unlock();
+ return new \WP_Error( 'thumbnail_creation_failed', __( 'The thumbnails failed to be created.', 'imagify' ) );
+ }
+
+ $optimization_level = $this->get_data()->get_optimization_level();
+
+ if ( false === $optimization_level ) {
+ $this->unlock();
+ return new \WP_Error( 'optimization_level_not_set', __( 'The optimization level of this media seems to have disappear from the database. You should restore this media and then launch a new optimization.', 'imagify' ) );
+ }
+
+ $args = [
+ 'hook_suffix' => 'optimize_missing_thumbnails',
+ 'locked' => true,
+ ];
+
+ // Optimize.
+ return $this->optimize_sizes( array_keys( $sizes ), $optimization_level, $args );
+ }
+
+ /**
+ * Create all missing thumbnails if they don't exist and update the attachment metadata.
+ *
+ * @since 1.9
+ * @access protected
+ * @see $this->get_missing_sizes()
+ * @author Grégory Viguier
+ *
+ * @param array $missing_sizes array {
+ * An array of data for the thumbnail sizes on success.
+ * Size names are used as array keys.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * @type bool $crop True to crop, false to resize.
+ * @type string $name The size name.
+ * @type string $file The name the thumbnail "should" have.
+ * }
+ * @return array {
+ * An array of thumbnail data (those without errors):
+ *
+ * @type string $file File name.
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * @type string $mime-type The mime type.
+ * }
+ */
+ protected function create_missing_thumbnails( $missing_sizes ) {
+ if ( ! $missing_sizes ) {
+ return [];
+ }
+
+ $media = $this->get_media();
+ $media_id = $media->get_id();
+ $metadata = wp_get_attachment_metadata( $media_id );
+ $metadata['sizes'] = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : [];
+
+ $destination_dir = $this->filesystem->dir_path( $media->get_raw_fullsize_path() );
+ $backup_file = new File( $media->get_backup_path() );
+ $without_errors = [];
+ $has_new_data = false;
+
+ // Create the missing thumbnails.
+ foreach ( $missing_sizes as $size_name => $thumbnail_data ) {
+ // The path to the destination file.
+ $thumbnail_data['path'] = $destination_dir . $thumbnail_data['file'];
+
+ if ( ! $this->filesystem->exists( $thumbnail_data['path'] ) ) {
+ $result = $backup_file->create_thumbnail( $thumbnail_data );
+
+ if ( is_array( $result ) ) {
+ // New file.
+ $metadata['sizes'][ $size_name ] = $result;
+ $has_new_data = true;
+ }
+ } else {
+ $result = true;
+ }
+
+ if ( ! empty( $metadata['sizes'][ $size_name ] ) && ! is_wp_error( $result ) ) {
+ // Not an error.
+ $without_errors[ $size_name ] = $metadata['sizes'][ $size_name ];
+ }
+ }
+
+ // Save the new data into the attachment metadata.
+ if ( $has_new_data ) {
+ /**
+ * Here we don't use wp_update_attachment_metadata() to prevent triggering unwanted hooks.
+ */
+ update_post_meta( $media_id, '_wp_attachment_metadata', $metadata );
+ }
+
+ return $without_errors;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Stats/OptimizedMediaWithoutWebp.php b/wp-content/plugins/imagify/classes/Stats/OptimizedMediaWithoutWebp.php
new file mode 100644
index 00000000..48915635
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Stats/OptimizedMediaWithoutWebp.php
@@ -0,0 +1,200 @@
+get_bulk_instance( $context )->has_optimized_media_without_webp();
+ }
+
+ return $stat;
+ }
+
+ /**
+ * Get and cache the number of optimized media without webp versions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int
+ */
+ public function get_cached_stat() {
+ $contexts = implode( '|', imagify_get_context_names() );
+ $stat = get_transient( static::NAME );
+
+ if ( isset( $stat['stat'], $stat['contexts'] ) && $stat['contexts'] === $contexts ) {
+ // The number is stored and the contexts are the same.
+ return (int) $stat['stat'];
+ }
+
+ $stat = [
+ 'contexts' => $contexts,
+ 'stat' => $this->get_stat(),
+ ];
+
+ set_transient( static::NAME, $stat, 2 * DAY_IN_SECONDS );
+
+ return $stat['stat'];
+ }
+
+ /**
+ * Clear the stat cache.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function clear_cache() {
+ delete_transient( static::NAME );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** HOOKS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Clear cache after optimizing a media.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process.
+ * @param array $item The item being processed.
+ */
+ public function maybe_clear_cache_after_optimization( $process, $item ) {
+ if ( ! $process->get_media()->is_image() || false === get_transient( static::NAME ) ) {
+ return;
+ }
+
+ $sizes = $process->get_data()->get_optimization_data();
+ $sizes = isset( $sizes['sizes'] ) ? (array) $sizes['sizes'] : [];
+ $new_sizes = array_flip( $item['sizes_done'] );
+ $new_sizes = array_intersect_key( $sizes, $new_sizes );
+ $size_name = 'full' . $process::WEBP_SUFFIX;
+
+ if ( ! isset( $new_sizes['full'] ) && ! empty( $new_sizes[ $size_name ]['success'] ) ) {
+ /**
+ * We just successfully generated the webp version of the full size.
+ * The full size was not optimized at the same time, that means it was optimized previously.
+ * Meaning: we just added a webp version to a media that was previously optimized, so there is one less optimized media without webp.
+ */
+ $this->clear_cache();
+ return;
+ }
+
+ if ( ! empty( $new_sizes['full']['success'] ) && empty( $new_sizes[ $size_name ]['success'] ) ) {
+ /**
+ * We now have a new optimized media without webp.
+ */
+ $this->clear_cache();
+ }
+ }
+
+ /**
+ * Clear cache after restoring a media.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process.
+ * @param bool|WP_Error $response The result of the operation: true on success, a WP_Error object on failure.
+ * @param array $files The list of files, before restoring them.
+ * @param array $data The optimization data, before deleting it.
+ */
+ public function maybe_clear_cache_after_restoration( $process, $response, $files, $data ) {
+ if ( ! $process->get_media()->is_image() || false === get_transient( static::NAME ) ) {
+ return;
+ }
+
+ $sizes = isset( $data['sizes'] ) ? (array) $data['sizes'] : [];
+ $size_name = 'full' . $process::WEBP_SUFFIX;
+
+ if ( ! empty( $sizes['full']['success'] ) && empty( $sizes[ $size_name ]['success'] ) ) {
+ /**
+ * This media had no webp versions.
+ */
+ $this->clear_cache();
+ }
+ }
+
+ /**
+ * Clear cache on media deletion.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process An optimization process.
+ */
+ public function maybe_clear_cache_on_deletion( $process ) {
+ if ( false === get_transient( static::NAME ) ) {
+ return;
+ }
+
+ $data = $process->get_data()->get_optimization_data();
+ $sizes = isset( $data['sizes'] ) ? (array) $data['sizes'] : [];
+ $size_name = 'full' . $process::WEBP_SUFFIX;
+
+ if ( ! empty( $sizes['full']['success'] ) && empty( $sizes[ $size_name ]['success'] ) ) {
+ /**
+ * This media had no webp versions.
+ */
+ $this->clear_cache();
+ }
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Stats/StatInterface.php b/wp-content/plugins/imagify/classes/Stats/StatInterface.php
new file mode 100644
index 00000000..3ca4dc8d
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Stats/StatInterface.php
@@ -0,0 +1,44 @@
+row ) ) {
+ return $this->row;
+ }
+
+ if ( ! $this->db_class_name || $this->id <= 0 ) {
+ return $this->invalidate_row();
+ }
+
+ $this->row = $this->get_row_db_instance()->get( $this->id );
+
+ if ( ! $this->row ) {
+ return $this->invalidate_row();
+ }
+
+ return $this->row;
+ }
+
+ /**
+ * Update the row.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $data The data to update.
+ */
+ public function update_row( $data ) {
+ if ( ! $this->db_class_name || $this->id <= 0 ) {
+ return;
+ }
+
+ $this->get_row_db_instance()->update( $this->id, $data );
+
+ $this->reset_row_cache();
+ }
+
+ /**
+ * Delete the row.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_row() {
+ if ( ! $this->db_class_name || $this->id <= 0 ) {
+ return;
+ }
+
+ $this->get_row_db_instance()->delete( $this->id );
+
+ $this->invalidate_row();
+ }
+
+ /**
+ * Shorthand to get the DB table instance.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return \Imagify\DB\DBInterface The DB table instance.
+ */
+ public function get_row_db_instance() {
+ return call_user_func( [ $this->db_class_name, 'get_instance' ] );
+ }
+
+ /**
+ * Invalidate the row, by setting it to an empty array.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array The row.
+ */
+ public function invalidate_row() {
+ $this->row = [];
+ return $this->row;
+ }
+
+ /**
+ * Reset the row cache.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return null The row.
+ */
+ public function reset_row_cache() {
+ $this->row = null;
+ return $this->row;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Webp/Apache.php b/wp-content/plugins/imagify/classes/Webp/Apache.php
new file mode 100644
index 00000000..440b6f0f
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Webp/Apache.php
@@ -0,0 +1,38 @@
+
+ AddType image/webp .webp
+' );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Webp/Display.php b/wp-content/plugins/imagify/classes/Webp/Display.php
new file mode 100644
index 00000000..1f141921
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Webp/Display.php
@@ -0,0 +1,257 @@
+init();
+ RewriteRules\Display::get_instance()->init();
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** HOOKS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * If display webp images, add the webp type to the .htaccess/etc file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $values The option values.
+ * @return array
+ */
+ public function maybe_add_rewrite_rules( $values ) {
+ $old_value = (bool) get_imagify_option( 'display_webp' );
+ // See \Imagify_Options->validate_values_on_update() for why we use 'convert_to_webp' here.
+ $new_value = ! empty( $values['display_webp'] ) && ! empty( $values['convert_to_webp'] );
+
+ if ( $old_value === $new_value ) {
+ // No changes.
+ return $values;
+ }
+
+ if ( ! $this->get_server_conf() ) {
+ return $values;
+ }
+
+ if ( $new_value ) {
+ // Add the webp file type.
+ $result = $this->get_server_conf()->add();
+ } else {
+ // Remove the webp file type.
+ $result = $this->get_server_conf()->remove();
+ }
+
+ if ( ! is_wp_error( $result ) ) {
+ return $values;
+ }
+
+ // Display an error message.
+ if ( is_multisite() && strpos( wp_get_referer(), network_admin_url( '/' ) ) === 0 ) {
+ \Imagify_Notices::get_instance()->add_network_temporary_notice( $result->get_error_message() );
+ } else {
+ \Imagify_Notices::get_instance()->add_site_temporary_notice( $result->get_error_message() );
+ }
+
+ return $values;
+ }
+
+ /**
+ * If the conf file is not writable, add a warning.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function maybe_add_webp_info() {
+ $conf = $this->get_server_conf();
+
+ if ( ! $conf ) {
+ return;
+ }
+
+ $writable = $conf->is_file_writable();
+
+ if ( ! is_wp_error( $writable ) ) {
+ return;
+ }
+
+ $rules = $conf->get_new_contents();
+
+ if ( ! $rules ) {
+ // Uh?
+ return;
+ }
+
+ echo ' ';
+
+ printf(
+ /* translators: %s is a file name. */
+ esc_html__( 'Imagify does not seem to be able to edit or create a %s file, you will have to add the following lines manually to it:', 'imagify' ),
+ '' . $this->get_file_path( true ) . ''
+ );
+
+ echo '' . esc_html( $rules ) . ' ';
+ }
+
+ /**
+ * Add rules on plugin activation.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function activate() {
+ $conf = $this->get_server_conf();
+
+ if ( ! $conf ) {
+ return;
+ }
+ if ( ! get_imagify_option( 'display_webp' ) ) {
+ return;
+ }
+ if ( is_wp_error( $conf->is_file_writable() ) ) {
+ return;
+ }
+
+ $conf->add();
+ }
+
+ /**
+ * Remove rules on plugin deactivation.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function deactivate() {
+ $conf = $this->get_server_conf();
+
+ if ( ! $conf ) {
+ return;
+ }
+ if ( ! get_imagify_option( 'display_webp' ) ) {
+ return;
+ }
+
+ $file_path = $conf->get_file_path();
+ $filesystem = \Imagify_Filesystem::get_instance();
+
+ if ( ! $filesystem->exists( $file_path ) ) {
+ return;
+ }
+ if ( ! $filesystem->is_writable( $file_path ) ) {
+ return;
+ }
+
+ $conf->remove();
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the path to the directory conf file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $relative True to get a path relative to the siteâs root.
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_file_path( $relative = false ) {
+ if ( ! $this->get_server_conf() ) {
+ return false;
+ }
+
+ $file_path = $this->get_server_conf()->get_file_path();
+
+ if ( $relative ) {
+ return \Imagify_Filesystem::get_instance()->make_path_relative( $file_path );
+ }
+
+ return $file_path;
+ }
+
+ /**
+ * Get the webp display method by validating the given value.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $values The option values.
+ * @return string 'picture' or 'rewrite'.
+ */
+ public function get_display_webp_method( $values ) {
+ $options = \Imagify_Options::get_instance();
+ $default = $options->get_default_values();
+ $default = $default['display_webp_method'];
+ $method = ! empty( $values['display_webp_method'] ) ? $values['display_webp_method'] : '';
+
+ return $options->sanitize_and_validate( 'display_webp_method', $method, $default );
+ }
+
+ /**
+ * Get the server conf instance.
+ * Note: nothing needed for nginx.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return \Imagify\WriteFile\WriteFileInterface
+ */
+ protected function get_server_conf() {
+ global $is_apache, $is_iis7;
+
+ if ( isset( $this->server_conf ) ) {
+ return $this->server_conf;
+ }
+
+ if ( $is_apache ) {
+ $this->server_conf = new Apache();
+ } elseif ( $is_iis7 ) {
+ $this->server_conf = new IIS();
+ } else {
+ $this->server_conf = false;
+ }
+
+ return $this->server_conf;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Webp/IIS.php b/wp-content/plugins/imagify/classes/Webp/IIS.php
new file mode 100644
index 00000000..97f8ecd2
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Webp/IIS.php
@@ -0,0 +1,39 @@
+
+
+
+ ' );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Webp/Picture/Display.php b/wp-content/plugins/imagify/classes/Webp/Picture/Display.php
new file mode 100644
index 00000000..2005c945
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Webp/Picture/Display.php
@@ -0,0 +1,752 @@
+ tags.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ */
+class Display {
+ use \Imagify\Traits\InstanceGetterTrait;
+
+ /**
+ * Option value.
+ *
+ * @var string
+ * @since 1.9
+ * @author Grégory Viguier
+ */
+ const OPTION_VALUE = 'picture';
+
+ /**
+ * Filesystem object.
+ *
+ * @var \Imagify_Filesystem
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $filesystem;
+
+ /**
+ * Constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function __construct() {
+ $this->filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Init.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function init() {
+ add_action( 'template_redirect', [ $this, 'start_content_process' ], -1000 );
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** ADD TAGS TO THE PAGE ========================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Start buffering the page content.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function start_content_process() {
+ if ( ! get_imagify_option( 'display_webp' ) ) {
+ return;
+ }
+
+ if ( self::OPTION_VALUE !== get_imagify_option( 'display_webp_method' ) ) {
+ return;
+ }
+
+ /**
+ * Prevent the replacement of tags into tags.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $allow True to allow the use of tags (default). False to prevent their use.
+ */
+ $allow = apply_filters( 'imagify_allow_picture_tags_for_webp', true );
+
+ if ( ! $allow ) {
+ return;
+ }
+
+ ob_start( [ $this, 'maybe_process_buffer' ] );
+ }
+
+ /**
+ * Maybe process the page content.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $buffer The buffer content.
+ * @return string
+ */
+ public function maybe_process_buffer( $buffer ) {
+ if ( ! $this->is_html( $buffer ) ) {
+ return $buffer;
+ }
+
+ if ( strlen( $buffer ) <= 255 ) {
+ // Buffer length must be > 255 (IE does not read pages under 255 c).
+ return $buffer;
+ }
+
+ $buffer = $this->process_content( $buffer );
+
+ /**
+ * Filter the page content after Imagify.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $buffer The page content.
+ */
+ $buffer = (string) apply_filters( 'imagify_buffer', $buffer );
+
+ return $buffer;
+ }
+
+ /**
+ * Process the content.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $content The content.
+ * @return string
+ */
+ public function process_content( $content ) {
+ $images = $this->get_images( $content );
+
+ if ( ! $images ) {
+ return $content;
+ }
+
+ foreach ( $images as $image ) {
+ $tag = $this->build_picture_tag( $image );
+ $content = str_replace( $image['tag'], $tag, $content );
+ }
+
+ return $content;
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BUILD HTML TAGS AND ATTRIBUTES ========================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Build the tag to insert.
+ *
+ * @since 1.9
+ * @see $this->process_image()
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $image An array of data.
+ * @return string A tag.
+ */
+ protected function build_picture_tag( $image ) {
+ $to_remove = [
+ 'alt' => '',
+ 'height' => '',
+ 'width' => '',
+ 'data-lazy-src' => '',
+ 'data-src' => '',
+ 'src' => '',
+ 'data-lazy-srcset' => '',
+ 'data-srcset' => '',
+ 'srcset' => '',
+ 'data-lazy-sizes' => '',
+ 'data-sizes' => '',
+ 'sizes' => '',
+ ];
+
+ $attributes = array_diff_key( $image['attributes'], $to_remove );
+
+ /**
+ * Filter the attributes to be added to the tag.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $attributes A list of attributes to be added to the tag.
+ * @param array $data Data built from the originale tag. See $this->process_image().
+ */
+ $attributes = apply_filters( 'imagify_picture_attributes', $attributes, $image );
+
+ $output = 'build_attributes( $attributes ) . ">\n";
+ /**
+ * Allow to add more tags to the tag.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $more_source_tags Additional tags.
+ * @param array $data Data built from the originale tag. See $this->process_image().
+ */
+ $output .= apply_filters( 'imagify_additional_source_tags', '', $image );
+ $output .= $this->build_source_tag( $image );
+ $output .= $this->build_img_tag( $image );
+ $output .= " \n";
+
+ return $output;
+ }
+
+ /**
+ * Build the tag to insert in the .
+ *
+ * @since 1.9
+ * @see $this->process_image()
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $image An array of data.
+ * @return string A tag.
+ */
+ protected function build_source_tag( $image ) {
+ $srcset_source = ! empty( $image['srcset_attribute'] ) ? $image['srcset_attribute'] : $image['src_attribute'] . 'set';
+ $attributes = [
+ 'type' => 'image/webp',
+ $srcset_source => [],
+ ];
+
+ if ( ! empty( $image['srcset'] ) ) {
+ foreach ( $image['srcset'] as $srcset ) {
+ if ( empty( $srcset['webp_url'] ) ) {
+ continue;
+ }
+
+ $attributes[ $srcset_source ][] = $srcset['webp_url'] . ' ' . $srcset['descriptor'];
+ }
+ }
+
+ if ( empty( $attributes[ $srcset_source ] ) ) {
+ $attributes[ $srcset_source ][] = $image['src']['webp_url'];
+ }
+
+ $attributes[ $srcset_source ] = implode( ', ', $attributes[ $srcset_source ] );
+
+ foreach ( [ 'data-lazy-srcset', 'data-srcset', 'srcset' ] as $srcset_attr ) {
+ if ( ! empty( $image['attributes'][ $srcset_attr ] ) && $srcset_attr !== $srcset_source ) {
+ $attributes[ $srcset_attr ] = $image['attributes'][ $srcset_attr ];
+ }
+ }
+
+ if ( 'srcset' !== $srcset_source && empty( $attributes['srcset'] ) && ! empty( $image['attributes']['src'] ) ) {
+ // Lazyload: the "src" attr should contain a placeholder (a data image or a blank.gif ).
+ $attributes['srcset'] = $image['attributes']['src'];
+ }
+
+ foreach ( [ 'data-lazy-sizes', 'data-sizes', 'sizes' ] as $sizes_attr ) {
+ if ( ! empty( $image['attributes'][ $sizes_attr ] ) ) {
+ $attributes[ $sizes_attr ] = $image['attributes'][ $sizes_attr ];
+ }
+ }
+
+ /**
+ * Filter the attributes to be added to the tag.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $attributes A list of attributes to be added to the tag.
+ * @param array $data Data built from the original tag. See $this->process_image().
+ */
+ $attributes = apply_filters( 'imagify_picture_source_attributes', $attributes, $image );
+
+ return 'build_attributes( $attributes ) . "/>\n";
+ }
+
+ /**
+ * Build the tag to insert in the .
+ *
+ * @since 1.9
+ * @see $this->process_image()
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $image An array of data.
+ * @return string A tag.
+ */
+ protected function build_img_tag( $image ) {
+ $to_remove = [
+ 'class' => '',
+ 'id' => '',
+ 'style' => '',
+ 'title' => '',
+ ];
+
+ $attributes = array_diff_key( $image['attributes'], $to_remove );
+
+ /**
+ * Filter the attributes to be added to the tag.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $attributes A list of attributes to be added to the tag.
+ * @param array $data Data built from the originale tag. See $this->process_image().
+ */
+ $attributes = apply_filters( 'imagify_picture_img_attributes', $attributes, $image );
+
+ return ' build_attributes( $attributes ) . "/>\n";
+ }
+
+ /**
+ * Create HTML attributes from an array.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $attributes A list of attribute pairs.
+ * @return string HTML attributes.
+ */
+ protected function build_attributes( $attributes ) {
+ if ( ! $attributes || ! is_array( $attributes ) ) {
+ return '';
+ }
+
+ $out = '';
+
+ foreach ( $attributes as $attribute => $value ) {
+ $out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"';
+ }
+
+ return $out;
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** VARIOUS TOOLS =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get a list of images in a content.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $content The content.
+ * @return array
+ */
+ protected function get_images( $content ) {
+ // Remove comments.
+ $content = preg_replace( '//Uis', '', $content );
+
+ if ( ! preg_match_all( '/ /isU', $content, $matches ) ) {
+ return [];
+ }
+
+ $images = array_map( [ $this, 'process_image' ], $matches[0] );
+ $images = array_filter( $images );
+
+ /**
+ * Filter the images to display with a tag.
+ *
+ * @since 1.9
+ * @see $this->process_image()
+ * @author Grégory Viguier
+ *
+ * @param array $images A list of arrays.
+ * @param string $content The page content.
+ */
+ $images = apply_filters( 'imagify_webp_picture_images_to_display', $images, $content );
+
+ if ( ! $images || ! is_array( $images ) ) {
+ return [];
+ }
+
+ foreach ( $images as $i => $image ) {
+ if ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) {
+ unset( $images[ $i ] );
+ continue;
+ }
+
+ unset( $images[ $i ]['src']['webp_path'], $images[ $i ]['src']['webp_exists'] );
+
+ if ( empty( $image['srcset'] ) || ! is_array( $image['srcset'] ) ) {
+ unset( $images[ $i ]['srcset'] );
+ continue;
+ }
+
+ foreach ( $image['srcset'] as $j => $srcset ) {
+ if ( ! is_array( $srcset ) ) {
+ continue;
+ }
+
+ if ( empty( $srcset['webp_exists'] ) || empty( $srcset['webp_url'] ) ) {
+ unset( $images[ $i ]['srcset'][ $j ]['webp_url'] );
+ }
+
+ unset( $images[ $i ]['srcset'][ $j ]['webp_path'], $images[ $i ]['srcset'][ $j ]['webp_exists'] );
+ }
+ }
+
+ return $images;
+ }
+
+ /**
+ * Process an image tag and get an array containing some data.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $image An image html tag.
+ * @return array|false {
+ * An array of data if the image has a webp version. False otherwise.
+ *
+ * @type string $tag The image tag.
+ * @type array $attributes The image attributes (minus src and srcset).
+ * @type array $src {
+ * @type string $url URL to the original image.
+ * @type string $webp_url URL to the webp version.
+ * }
+ * @type array $srcset {
+ * An array or arrays. Not set if not applicable.
+ *
+ * @type string $url URL to the original image.
+ * @type string $webp_url URL to the webp version. Not set if not applicable.
+ * @type string $descriptor A src descriptor.
+ * }
+ * }
+ */
+ protected function process_image( $image ) {
+ static $extensions;
+
+ $atts_pattern = '/(?[^\s"\']+)\s*=\s*(["\'])\s*(?.*?)\s*\2/s';
+
+ if ( ! preg_match_all( $atts_pattern, $image, $tmp_attributes, PREG_SET_ORDER ) ) {
+ // No attributes?
+ return false;
+ }
+
+ $attributes = [];
+
+ foreach ( $tmp_attributes as $attribute ) {
+ $attributes[ $attribute['name'] ] = $attribute['value'];
+ }
+
+ if ( ! empty( $attributes['class'] ) && strpos( $attributes['class'], 'imagify-no-webp' ) !== false ) {
+ // Has the 'imagify-no-webp' class.
+ return false;
+ }
+
+ // Deal with the src attribute.
+ $src_source = false;
+
+ foreach ( [ 'data-lazy-src', 'data-src', 'src' ] as $src_attr ) {
+ if ( ! empty( $attributes[ $src_attr ] ) ) {
+ $src_source = $src_attr;
+ break;
+ }
+ }
+
+ if ( ! $src_source ) {
+ // No src attribute.
+ return false;
+ }
+
+ if ( ! isset( $extensions ) ) {
+ $extensions = imagify_get_mime_types( 'image' );
+ $extensions = array_keys( $extensions );
+ $extensions = implode( '|', $extensions );
+ }
+
+ if ( ! preg_match( '@^(?(?:(?:https?:)?//|/).+\.(?' . $extensions . '))(?\?.*)?$@i', $attributes[ $src_source ], $src ) ) {
+ // Not a supported image format.
+ return false;
+ }
+
+ $webp_url = imagify_path_to_webp( $src['src'] );
+ $webp_path = $this->url_to_path( $webp_url );
+ $webp_url .= ! empty( $src['query'] ) ? $src['query'] : '';
+
+ $data = [
+ 'tag' => $image,
+ 'attributes' => $attributes,
+ 'src_attribute' => $src_source,
+ 'src' => [
+ 'url' => $attributes[ $src_source ],
+ 'webp_url' => $webp_url,
+ 'webp_path' => $webp_path,
+ 'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ),
+ ],
+ 'srcset_attribute' => false,
+ 'srcset' => [],
+ ];
+
+ // Deal with the srcset attribute.
+ $srcset_source = false;
+
+ foreach ( [ 'data-lazy-srcset', 'data-srcset', 'srcset' ] as $srcset_attr ) {
+ if ( ! empty( $attributes[ $srcset_attr ] ) ) {
+ $srcset_source = $srcset_attr;
+ break;
+ }
+ }
+
+ if ( $srcset_source ) {
+ $data['srcset_attribute'] = $srcset_source;
+
+ $srcset = explode( ',', $attributes[ $srcset_source ] );
+
+ foreach ( $srcset as $srcs ) {
+ $srcs = preg_split( '/\s+/', trim( $srcs ) );
+
+ if ( count( $srcs ) > 2 ) {
+ // Not a good idea to have space characters in file name.
+ $descriptor = array_pop( $srcs );
+ $srcs = [ implode( ' ', $srcs ), $descriptor ];
+ }
+
+ if ( empty( $srcs[1] ) ) {
+ $srcs[1] = '1x';
+ }
+
+ if ( ! preg_match( '@^(?(?:https?:)?//.+\.(?' . $extensions . '))(?\?.*)?$@i', $srcs[0], $src ) ) {
+ // Not a supported image format.
+ $data['srcset'][] = [
+ 'url' => $srcs[0],
+ 'descriptor' => $srcs[1],
+ ];
+ continue;
+ }
+
+ $webp_url = imagify_path_to_webp( $src['src'] );
+ $webp_path = $this->url_to_path( $webp_url );
+ $webp_url .= ! empty( $src['query'] ) ? $src['query'] : '';
+
+ $data['srcset'][] = [
+ 'url' => $srcs[0],
+ 'descriptor' => $srcs[1],
+ 'webp_url' => $webp_url,
+ 'webp_path' => $webp_path,
+ 'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ),
+ ];
+ }
+ }
+
+ /**
+ * Filter a processed image tag.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $data An array of data for this image.
+ * @param string $image An image html tag.
+ */
+ $data = apply_filters( 'imagify_webp_picture_process_image', $data, $image );
+
+ if ( ! $data || ! is_array( $data ) ) {
+ return false;
+ }
+
+ if ( ! isset( $data['tag'], $data['attributes'], $data['src_attribute'], $data['src'], $data['srcset_attribute'], $data['srcset'] ) ) {
+ return false;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Tell if a content is HTML.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $content The content.
+ * @return bool
+ */
+ protected function is_html( $content ) {
+ return preg_match( '/<\/html>/i', $content );
+ }
+
+ /**
+ * Convert a file URL to an absolute path.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $url A file URL.
+ * @return string|bool The file path. False on failure.
+ */
+ protected function url_to_path( $url ) {
+ static $uploads_url;
+ static $uploads_dir;
+ static $root_url;
+ static $root_dir;
+ static $cdn_url;
+ static $domain_url;
+
+ /**
+ * $url, $uploads_url, $root_url, and $cdn_url are passed through `set_url_scheme()` only to make sure `stripos()` doesn't fail over a stupid http/https difference.
+ */
+ if ( ! isset( $uploads_url ) ) {
+ $uploads_url = set_url_scheme( $this->filesystem->get_upload_baseurl() );
+ $uploads_dir = $this->filesystem->get_upload_basedir( true );
+ $root_url = set_url_scheme( $this->filesystem->get_site_root_url() );
+ $root_dir = $this->filesystem->get_site_root();
+ $cdn_url = $this->get_cdn_source();
+ $cdn_url = $cdn_url['url'] ? set_url_scheme( $cdn_url['url'] ) : false;
+ $domain_url = wp_parse_url( $root_url );
+
+ if ( ! empty( $domain_url['scheme'] ) && ! empty( $domain_url['host'] ) ) {
+ $domain_url = $domain_url['scheme'] . '://' . $domain_url['host'] . '/';
+ } else {
+ $domain_url = false;
+ }
+ }
+
+ // Get the right URL format.
+ if ( $domain_url && strpos( $url, '/' ) === 0 ) {
+ // URL like `/path/to/image.jpg.webp`.
+ $url = $domain_url . ltrim( $url, '/' );
+ }
+
+ $url = set_url_scheme( $url );
+
+ if ( $cdn_url && $domain_url && stripos( $url, $cdn_url ) === 0 ) {
+ // CDN.
+ $url = str_ireplace( $cdn_url, $domain_url, $url );
+ }
+
+ // Return the path.
+ if ( stripos( $url, $uploads_url ) === 0 ) {
+ return str_ireplace( $uploads_url, $uploads_dir, $url );
+ }
+
+ if ( stripos( $url, $root_url ) === 0 ) {
+ return str_ireplace( $root_url, $root_dir, $url );
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the CDN "source".
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $option_url An URL to use instead of the one stored in the option. It is used only if no constant/filter.
+ * @return array {
+ * @type string $source Where does it come from? Possible values are 'constant', 'filter', or 'option'.
+ * @type string $name Who? Can be a constant name, a plugin name, or an empty string.
+ * @type string $url The CDN URL, with a trailing slash. An empty string if no URL is set.
+ * }
+ */
+ public function get_cdn_source( $option_url = '' ) {
+ if ( defined( 'IMAGIFY_CDN_URL' ) && IMAGIFY_CDN_URL && is_string( IMAGIFY_CDN_URL ) ) {
+ // Use a constant.
+ $source = [
+ 'source' => 'constant',
+ 'name' => 'IMAGIFY_CDN_URL',
+ 'url' => IMAGIFY_CDN_URL,
+ ];
+ } else {
+ // Maybe use a filter.
+ $filter_source = [
+ 'name' => null,
+ 'url' => null,
+ ];
+
+ /**
+ * Provide a custom CDN source.
+ *
+ * @since 1.9.3
+ * @author Grégory Viguier
+ *
+ * @param array $filter_source {
+ * @type $name string The name of which provides the URL (plugin name, etc).
+ * @type $url string The CDN URL.
+ * }
+ */
+ $filter_source = apply_filters( 'imagify_cdn_source', $filter_source );
+
+ if ( ! empty( $filter_source['url'] ) ) {
+ $source = [
+ 'source' => 'filter',
+ 'name' => ! empty( $filter_source['name'] ) ? $filter_source['name'] : '',
+ 'url' => $filter_source['url'],
+ ];
+ }
+ }
+
+ if ( empty( $source['url'] ) ) {
+ // No constant, no filter: use the option.
+ $source = [
+ 'source' => 'option',
+ 'name' => '',
+ 'url' => $option_url && is_string( $option_url ) ? $option_url : get_imagify_option( 'cdn_url' ),
+ ];
+ }
+
+ if ( empty( $source['url'] ) ) {
+ // Nothing set.
+ return [
+ 'source' => 'option',
+ 'name' => '',
+ 'url' => '',
+ ];
+ }
+
+ $source['url'] = $this->sanitize_cdn_url( $source['url'] );
+
+ if ( empty( $source['url'] ) ) {
+ // Not an URL.
+ return [
+ 'source' => 'option',
+ 'name' => '',
+ 'url' => '',
+ ];
+ }
+
+ return $source;
+ }
+
+ /**
+ * Sanitize the CDN URL value.
+ *
+ * @since 1.9.3
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $url The URL to sanitize.
+ * @return string
+ */
+ public function sanitize_cdn_url( $url ) {
+ $url = sanitize_text_field( $url );
+
+ if ( ! $url || ! preg_match( '@^https?://.+\.[^.]+@i', $url ) ) {
+ // Not an URL.
+ return '';
+ }
+
+ return trailingslashit( $url );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Webp/RewriteRules/Apache.php b/wp-content/plugins/imagify/classes/Webp/RewriteRules/Apache.php
new file mode 100644
index 00000000..bcf87a4a
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Webp/RewriteRules/Apache.php
@@ -0,0 +1,62 @@
+get_extensions_pattern();
+ $home_root = wp_parse_url( home_url( '/' ) );
+ $home_root = $home_root['path'];
+
+ return trim( '
+
+ # Vary: Accept for all the requests to jpeg, png, and gif.
+ SetEnvIf Request_URI "\.(' . $extensions . ')$" REQUEST_image
+
+
+
+ RewriteEngine On
+ RewriteBase ' . $home_root . '
+
+ # Check if browser supports WebP images.
+ RewriteCond %{HTTP_ACCEPT} image/webp
+
+ # Check if WebP replacement image exists.
+ RewriteCond %{REQUEST_FILENAME}.webp -f
+
+ # Serve WebP image instead.
+ RewriteRule (.+)\.(' . $extensions . ')$ $1.$2.webp [T=image/webp,NC]
+
+
+
+ Header append Vary Accept env=REQUEST_image
+ ' );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Webp/RewriteRules/Display.php b/wp-content/plugins/imagify/classes/Webp/RewriteRules/Display.php
new file mode 100644
index 00000000..d3008d9c
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Webp/RewriteRules/Display.php
@@ -0,0 +1,268 @@
+validate_values_on_update() for why we use 'convert_to_webp' here.
+ $is_enabled = ! empty( $values['display_webp'] ) && ! empty( $values['convert_to_webp'] );
+
+ // Which method?
+ $old_value = get_imagify_option( 'display_webp_method' );
+ $new_value = ! empty( $values['display_webp_method'] ) ? $values['display_webp_method'] : '';
+
+ // Decide when to add or remove rules.
+ $is_rewrite = self::OPTION_VALUE === $new_value;
+ $was_rewrite = self::OPTION_VALUE === $old_value;
+ $add_or_remove = false;
+
+ if ( $is_enabled && $is_rewrite && ( ! $was_enabled || ! $was_rewrite ) ) {
+ // Display webp & use rewrite method, but only if one of the values changed: add rules.
+ $add_or_remove = 'add';
+ } elseif ( $was_enabled && $was_rewrite && ( ! $is_enabled || ! $is_rewrite ) ) {
+ // Was displaying webp & was using rewrite method, but only if one of the values changed: remove rules.
+ $add_or_remove = 'remove';
+ } else {
+ return $values;
+ }
+
+ if ( $is_apache ) {
+ $rules = new Apache();
+ } elseif ( $is_iis7 ) {
+ $rules = new IIS();
+ } elseif ( $is_nginx ) {
+ $rules = new Nginx();
+ } else {
+ return $values;
+ }
+
+ if ( 'add' === $add_or_remove ) {
+ // Add the rewrite rules.
+ $result = $rules->add();
+ } else {
+ // Remove the rewrite rules.
+ $result = $rules->remove();
+ }
+
+ if ( ! is_wp_error( $result ) ) {
+ return $values;
+ }
+
+ // Display an error message.
+ if ( is_multisite() && strpos( wp_get_referer(), network_admin_url( '/' ) ) === 0 ) {
+ \Imagify_Notices::get_instance()->add_network_temporary_notice( $result->get_error_message() );
+ } else {
+ \Imagify_Notices::get_instance()->add_site_temporary_notice( $result->get_error_message() );
+ }
+
+ return $values;
+ }
+
+ /**
+ * If the conf file is not writable, add a warning.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function maybe_add_webp_info() {
+ global $is_nginx;
+
+ $conf = $this->get_server_conf();
+
+ if ( ! $conf ) {
+ return;
+ }
+
+ $writable = $conf->is_file_writable();
+
+ if ( is_wp_error( $writable ) ) {
+ $rules = $conf->get_new_contents();
+
+ if ( ! $rules ) {
+ // Uh?
+ return;
+ }
+
+ printf(
+ /* translators: %s is a file name. */
+ esc_html__( 'If you choose to use rewrite rules, you will have to add the following lines manually to the %s file:', 'imagify' ),
+ '' . $this->get_file_path( true ) . ''
+ );
+
+ echo '' . esc_html( $rules ) . ' ';
+ } elseif ( $is_nginx ) {
+ printf(
+ /* translators: %s is a file name. */
+ esc_html__( 'If you choose to use rewrite rules, the file %s will be created and must be included into the serverâs configuration file (then restart the server).', 'imagify' ),
+ '' . $this->get_file_path( true ) . ''
+ );
+ }
+ }
+
+ /**
+ * Add rules on plugin activation.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function activate() {
+ $conf = $this->get_server_conf();
+
+ if ( ! $conf ) {
+ return;
+ }
+ if ( ! get_imagify_option( 'display_webp' ) ) {
+ return;
+ }
+ if ( self::OPTION_VALUE !== get_imagify_option( 'display_webp_method' ) ) {
+ return;
+ }
+ if ( is_wp_error( $conf->is_file_writable() ) ) {
+ return;
+ }
+
+ $conf->add();
+ }
+
+ /**
+ * Remove rules on plugin deactivation.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function deactivate() {
+ $conf = $this->get_server_conf();
+
+ if ( ! $conf ) {
+ return;
+ }
+ if ( ! get_imagify_option( 'display_webp' ) ) {
+ return;
+ }
+ if ( self::OPTION_VALUE !== get_imagify_option( 'display_webp_method' ) ) {
+ return;
+ }
+
+ $file_path = $conf->get_file_path();
+ $filesystem = \Imagify_Filesystem::get_instance();
+
+ if ( ! $filesystem->exists( $file_path ) ) {
+ return;
+ }
+ if ( ! $filesystem->is_writable( $file_path ) ) {
+ return;
+ }
+
+ $conf->remove();
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the path to the directory conf file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $relative True to get a path relative to the siteâs root.
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_file_path( $relative = false ) {
+ if ( ! $this->get_server_conf() ) {
+ return false;
+ }
+
+ $file_path = $this->get_server_conf()->get_file_path();
+
+ if ( $relative ) {
+ return \Imagify_Filesystem::get_instance()->make_path_relative( $file_path );
+ }
+
+ return $file_path;
+ }
+
+ /**
+ * Get the server conf instance.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return \Imagify\WriteFile\WriteFileInterface
+ */
+ protected function get_server_conf() {
+ global $is_apache, $is_iis7, $is_nginx;
+
+ if ( isset( $this->server_conf ) ) {
+ return $this->server_conf;
+ }
+
+ if ( $is_apache ) {
+ $this->server_conf = new Apache();
+ } elseif ( $is_iis7 ) {
+ $this->server_conf = new IIS();
+ } elseif ( $is_nginx ) {
+ $this->server_conf = new Nginx();
+ } else {
+ $this->server_conf = false;
+ }
+
+ return $this->server_conf;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Webp/RewriteRules/IIS.php b/wp-content/plugins/imagify/classes/Webp/RewriteRules/IIS.php
new file mode 100644
index 00000000..935ddc0a
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Webp/RewriteRules/IIS.php
@@ -0,0 +1,63 @@
+get_extensions_pattern();
+ $home_root = wp_parse_url( home_url( '/' ) );
+ $home_root = $home_root['path'];
+
+ return trim( '
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ' );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/Webp/RewriteRules/Nginx.php b/wp-content/plugins/imagify/classes/Webp/RewriteRules/Nginx.php
new file mode 100644
index 00000000..e68cda13
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/Webp/RewriteRules/Nginx.php
@@ -0,0 +1,52 @@
+get_extensions_pattern();
+ $home_root = wp_parse_url( home_url( '/' ) );
+ $home_root = $home_root['path'];
+
+ return trim( '
+location ~* ^(' . $home_root . '.+)\.(' . $extensions . ')$ {
+ add_header Vary Accept;
+
+ if ($http_accept ~* "webp"){
+ set $imwebp A;
+ }
+ if (-f $request_filename.webp) {
+ set $imwebp "${imwebp}B";
+ }
+ if ($imwebp = AB) {
+ rewrite ^(.*) $1.webp;
+ }
+}' );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/WriteFile/AbstractApacheDirConfFile.php b/wp-content/plugins/imagify/classes/WriteFile/AbstractApacheDirConfFile.php
new file mode 100644
index 00000000..97e826ec
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/WriteFile/AbstractApacheDirConfFile.php
@@ -0,0 +1,77 @@
+get_file_contents();
+
+ if ( is_wp_error( $contents ) ) {
+ return $contents;
+ }
+
+ $start_marker = '# BEGIN ' . static::TAG_NAME;
+ $end_marker = '# END ' . static::TAG_NAME;
+
+ // Remove previous rules.
+ $contents = preg_replace( '/\s*?' . preg_quote( $start_marker, '/' ) . '.*' . preg_quote( $end_marker, '/' ) . '\s*?/isU', "\n\n", $contents );
+ $contents = trim( $contents );
+
+ if ( $new_contents ) {
+ $contents = $new_contents . "\n\n" . $contents;
+ }
+
+ return $this->put_file_contents( $contents );
+ }
+
+ /**
+ * Get new contents to write into the file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_new_contents() {
+ $contents = parent::get_new_contents();
+
+ if ( ! $contents ) {
+ return '';
+ }
+
+ return '# BEGIN ' . static::TAG_NAME . "\n" . $contents . "\n# END " . static::TAG_NAME;
+ }
+
+ /**
+ * Get the unfiltered path to the file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ protected function get_raw_file_path() {
+ return $this->filesystem->get_site_root() . '.htaccess';
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/WriteFile/AbstractIISDirConfFile.php b/wp-content/plugins/imagify/classes/WriteFile/AbstractIISDirConfFile.php
new file mode 100644
index 00000000..0c1f8c1b
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/WriteFile/AbstractIISDirConfFile.php
@@ -0,0 +1,283 @@
+get_file_contents();
+
+ if ( is_wp_error( $doc ) ) {
+ return $doc;
+ }
+
+ $marker = static::TAG_NAME;
+ $xpath = new \DOMXPath( $doc );
+
+ // Remove previous rules.
+ $old_nodes = $xpath->query( ".//*[starts-with(@name,'$marker')]" );
+
+ if ( $old_nodes->length > 0 ) {
+ foreach ( $old_nodes as $old_node ) {
+ $old_node->parentNode->removeChild( $old_node );
+ }
+ }
+
+ // No new contents? Stop here.
+ if ( ! $new_contents ) {
+ return $this->put_file_contents( $doc );
+ }
+
+ $new_contents = preg_split( '//', $new_contents, -1, PREG_SPLIT_DELIM_CAPTURE );
+ unset( $new_contents[0] );
+ $new_contents = array_chunk( $new_contents, 2 );
+
+ foreach ( $new_contents as $i => $new_content ) {
+ $path = rtrim( $new_content[0], '/' );
+ $new_content = trim( $new_content[1] );
+
+ if ( '' === $new_content ) {
+ continue;
+ }
+
+ $fragment = $doc->createDocumentFragment();
+ $fragment->appendXML( $new_content );
+
+ $this->get_node( $doc, $xpath, $path, $fragment );
+ }
+
+ return $this->put_file_contents( $doc );
+ }
+
+ /**
+ * Get the unfiltered path to the file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ protected function get_raw_file_path() {
+ return $this->filesystem->get_site_root() . 'web.config';
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OTHER TOOLS ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if the file is writable.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|\WP_Error True if writable. A \WP_Error object if not.
+ */
+ public function is_file_writable() {
+ $file_path = $this->get_file_path();
+ $file_name = $this->filesystem->make_path_relative( $file_path );
+
+ if ( $this->is_conf_edition_disabled() ) {
+ return new \WP_Error(
+ 'edition_disabled',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'Edition of the %s file is disabled.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+
+ if ( ! class_exists( '\DOMDocument' ) ) {
+ return new \WP_Error(
+ 'not_domdocument',
+ sprintf(
+ /* translators: 1 is a php class name, 2 is a file name. */
+ __( 'The class %1$s is not present on your server, a %2$s file cannot be created nor edited.', 'imagify' ),
+ 'DOMDocument',
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+
+ if ( ! $this->filesystem->exists( $file_path ) ) {
+ $dir_path = $this->filesystem->dir_path( $file_path );
+
+ $this->filesystem->make_dir( $dir_path );
+
+ if ( ! $this->filesystem->is_writable( $dir_path ) ) {
+ return new \WP_Error(
+ 'parent_not_writable',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( '%sâs parent folder is not writable.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+ if ( ! $this->filesystem->exists( $file_path ) ) {
+ $result = $this->filesystem->put_contents( $file_path, ' ' );
+
+ if ( ! $result ) {
+ return new \WP_Error(
+ 'not_created',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'The %s file could not be created.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+ }
+ } elseif ( ! $this->filesystem->is_writable( $file_path ) ) {
+ return new \WP_Error(
+ 'not_writable',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'The %s file is not writable.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the file contents.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return \DOMDocument|\WP_Error A \DOMDocument object on success, a \WP_Error object on failure.
+ */
+ protected function get_file_contents() {
+ $writable = $this->is_file_writable();
+
+ if ( is_wp_error( $writable ) ) {
+ return $writable;
+ }
+
+ $file_path = $this->get_file_path();
+ $doc = new \DOMDocument();
+
+ $doc->preserveWhiteSpace = false;
+
+ if ( false === $doc->load( $file_path ) ) {
+ $file_path = $this->get_file_path();
+ $file_name = $this->filesystem->make_path_relative( $file_path );
+
+ return new \WP_Error(
+ 'not_read',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'The %s file could not be read.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+
+ return $doc;
+ }
+
+ /**
+ * Put new contents into the file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param \DOMDocument $contents A \DOMDocument object.
+ * @return bool|\WP_Error True on success, a \WP_Error object on failure.
+ */
+ protected function put_file_contents( $contents ) {
+ $contents->encoding = 'UTF-8';
+ $contents->formatOutput = true;
+
+ saveDomDocument( $contents, $this->get_file_path() );
+
+ return true;
+ }
+
+
+ /**
+ * Get a DOMNode node.
+ * If it does not exist it is created recursively.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param \DOMDocument $doc A \DOMDocument element.
+ * @param \DOMXPath $xpath A \DOMXPath element.
+ * @param string $path Path to the desired node.
+ * @param \DOMNode $child A \DOMNode to be prepended.
+ * @return \DOMNode The \DOMNode node.
+ */
+ protected function get_node( $doc, $xpath, $path, $child ) {
+ $nodelist = $xpath->query( $path );
+
+ if ( $nodelist->length > 0 ) {
+ return $this->prepend_node( $nodelist->item( 0 ), $child );
+ }
+
+ $path = explode( '/', $path );
+ $node = array_pop( $path );
+ $path = implode( '/', $path );
+
+ $final_node = $doc->createElement( $node );
+
+ if ( $child ) {
+ $final_node->appendChild( $child );
+ }
+
+ return $this->get_node( $doc, $xpath, $path, $final_node );
+ }
+
+
+ /**
+ * Prepend a DOMNode node.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param \DOMNode $container_node The \DOMNode that will contain the new node.
+ * @param \DOMNode $new_node The \DOMNode to be prepended.
+ * @return \DOMNode The \DOMNode containing the new node.
+ */
+ protected function prepend_node( $container_node, $new_node ) {
+ if ( ! $new_node ) {
+ return $container_node;
+ }
+
+ if ( $container_node->hasChildNodes() ) {
+ $container_node->insertBefore( $new_node, $container_node->firstChild );
+ } else {
+ $container_node->appendChild( $new_node );
+ }
+
+ return $container_node;
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/WriteFile/AbstractNginxDirConfFile.php b/wp-content/plugins/imagify/classes/WriteFile/AbstractNginxDirConfFile.php
new file mode 100644
index 00000000..954d7469
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/WriteFile/AbstractNginxDirConfFile.php
@@ -0,0 +1,77 @@
+get_file_contents();
+
+ if ( is_wp_error( $contents ) ) {
+ return $contents;
+ }
+
+ $start_marker = '# BEGIN ' . static::TAG_NAME;
+ $end_marker = '# END ' . static::TAG_NAME;
+
+ // Remove previous rules.
+ $contents = preg_replace( '/\s*?' . preg_quote( $start_marker, '/' ) . '.*' . preg_quote( $end_marker, '/' ) . '\s*?/isU', "\n\n", $contents );
+ $contents = trim( $contents );
+
+ if ( $new_contents ) {
+ $contents = $new_contents . "\n\n" . $contents;
+ }
+
+ return $this->put_file_contents( $contents );
+ }
+
+ /**
+ * Get new contents to write into the file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_new_contents() {
+ $contents = parent::get_new_contents();
+
+ if ( ! $contents ) {
+ return '';
+ }
+
+ return '# BEGIN ' . static::TAG_NAME . "\n" . $contents . "\n# END " . static::TAG_NAME;
+ }
+
+ /**
+ * Get the unfiltered path to the file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ protected function get_raw_file_path() {
+ return $this->filesystem->get_site_root() . 'conf/imagify.conf';
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/WriteFile/AbstractWriteDirConfFile.php b/wp-content/plugins/imagify/classes/WriteFile/AbstractWriteDirConfFile.php
new file mode 100644
index 00000000..83a898d7
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/WriteFile/AbstractWriteDirConfFile.php
@@ -0,0 +1,395 @@
+filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Add new contents to the file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|\WP_Error True on success. A \WP_Error object on error.
+ */
+ public function add() {
+ $result = $this->insert_contents( $this->get_new_contents() );
+
+ if ( ! is_wp_error( $result ) ) {
+ return true;
+ }
+ $file_path = $this->get_file_path();
+ $file_name = $this->filesystem->make_path_relative( $file_path );
+
+ if ( 'edition_disabled' === $result->get_error_code() ) {
+ return new \WP_Error(
+ 'edition_disabled',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'Imagify did not add contents to the %s file, as its edition is disabled.', 'imagify' ),
+ $file_name
+ )
+ );
+ }
+
+ return new \WP_Error(
+ 'add_contents_failure',
+ sprintf(
+ /* translators: 1 is a file name, 2 is an error message. */
+ __( 'Imagify could not insert contents into the %1$s file: %2$s', 'imagify' ),
+ $file_name,
+ $result->get_error_message()
+ ),
+ [ 'code' => $result->get_error_code() ]
+ );
+ }
+
+ /**
+ * Remove the related contents from the file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|\WP_Error True on success. A \WP_Error object on error.
+ */
+ public function remove() {
+ $result = $this->insert_contents( '' );
+
+ if ( ! is_wp_error( $result ) ) {
+ return true;
+ }
+
+ $file_name = $this->filesystem->make_path_relative( $file_path );
+
+ if ( 'edition_disabled' === $result->get_error_code() ) {
+ return new \WP_Error(
+ 'edition_disabled',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'Imagify did not remove the contents from the %s file, as its edition is disabled.', 'imagify' ),
+ $file_name
+ )
+ );
+ }
+
+ return new \WP_Error(
+ 'add_contents_failure',
+ sprintf(
+ /* translators: 1 is a file name, 2 is an error message. */
+ __( 'Imagify could not remove contents from the %1$s file: %2$s', 'imagify' ),
+ $file_name,
+ $result->get_error_message()
+ ),
+ [ 'code' => $result->get_error_code() ]
+ );
+ }
+
+ /**
+ * Get the path to the file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_file_path() {
+ $file_path = $this->get_raw_file_path();
+
+ /**
+ * Filter the path to the directory conf file.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ */
+ $new_file_path = apply_filters( 'imagify_dir_conf_path', $file_path );
+
+ if ( $new_file_path && is_string( $new_file_path ) ) {
+ return $new_file_path;
+ }
+
+ return $file_path;
+ }
+
+ /**
+ * Tell if the file is writable.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|\WP_Error True if writable. A \WP_Error object if not.
+ */
+ public function is_file_writable() {
+ $file_path = $this->get_file_path();
+ $file_name = $this->filesystem->make_path_relative( $file_path );
+
+ if ( $this->is_conf_edition_disabled() ) {
+ return new \WP_Error(
+ 'edition_disabled',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'Edition of the %s file is disabled.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+
+ if ( ! $this->filesystem->exists( $file_path ) ) {
+ $dir_path = $this->filesystem->dir_path( $file_path );
+
+ $this->filesystem->make_dir( $dir_path );
+
+ if ( ! $this->filesystem->is_writable( $dir_path ) ) {
+ return new \WP_Error(
+ 'parent_not_writable',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( '%sâs parent folder is not writable.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+ if ( ! $this->filesystem->touch( $file_path ) ) {
+ return new \WP_Error(
+ 'not_created',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'The %s file could not be created.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+ } elseif ( ! $this->filesystem->is_writable( $file_path ) ) {
+ return new \WP_Error(
+ 'not_writable',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'The %s file is not writable.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Get new contents to write into the file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_new_contents() {
+ $contents = $this->get_raw_new_contents();
+
+ /**
+ * Filter the contents to add to the directory conf file.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $contents The contents.
+ */
+ $new_contents = apply_filters( 'imagify_dir_conf_contents', $contents );
+
+ if ( $new_contents && is_string( $new_contents ) ) {
+ return $new_contents;
+ }
+
+ return $contents;
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** ABSTRACT METHODS ======================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Insert new contents into the directory conf file.
+ * Replaces existing marked info. Creates file if none exists.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $new_contents Contents to insert.
+ * @return bool|\WP_Error True on write success, a \WP_Error object on failure.
+ */
+ abstract protected function insert_contents( $new_contents );
+
+ /**
+ * Get the unfiltered path to the file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ abstract protected function get_raw_file_path();
+
+ /**
+ * Get unfiltered new contents to write into the file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ abstract protected function get_raw_new_contents();
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OTHER TOOLS ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the file contents.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return mixed|\WP_Error The file contents on success, a \WP_Error object on failure.
+ */
+ protected function get_file_contents() {
+ $writable = $this->is_file_writable();
+
+ if ( is_wp_error( $writable ) ) {
+ return $writable;
+ }
+
+ $file_path = $this->get_file_path();
+
+ if ( ! $this->filesystem->exists( $file_path ) ) {
+ // This should not happen.
+ return '';
+ }
+
+ $contents = $this->filesystem->get_contents( $file_path );
+
+ if ( false === $contents ) {
+ return new \WP_Error(
+ 'not_read',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'The %s file could not be read.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+
+ return $contents;
+ }
+
+ /**
+ * Put new contents into the file.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $contents New contents to add to the file.
+ * @return bool|\WP_Error True on success, a \WP_Error object on failure.
+ */
+ protected function put_file_contents( $contents ) {
+ $file_path = $this->get_file_path();
+ $result = $this->filesystem->put_contents( $file_path, $contents );
+
+ if ( $result ) {
+ return true;
+ }
+
+ $file_name = $this->filesystem->make_path_relative( $file_path );
+
+ return new \WP_Error(
+ 'edition_failed',
+ sprintf(
+ /* translators: %s is a file name. */
+ __( 'Could not write into the %s file.', 'imagify' ),
+ '' . esc_html( $file_name ) . ''
+ )
+ );
+ }
+
+ /**
+ * Tell if edition of the directory conf file is disabled.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool True to disable, false otherwise.
+ */
+ protected function is_conf_edition_disabled() {
+ /**
+ * Disable directory conf edition.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $disable True to disable, false otherwise.
+ */
+ return (bool) apply_filters( 'imagify_disable_dir_conf_edition', false );
+ }
+
+ /**
+ * Get a regex pattern to be used to match the supported file extensions.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ protected function get_extensions_pattern() {
+ $extensions = imagify_get_mime_types( 'image' );
+ $extensions = array_keys( $extensions );
+
+ return implode( '|', $extensions );
+ }
+}
diff --git a/wp-content/plugins/imagify/classes/WriteFile/WriteFileInterface.php b/wp-content/plugins/imagify/classes/WriteFile/WriteFileInterface.php
new file mode 100644
index 00000000..e1e02dcd
--- /dev/null
+++ b/wp-content/plugins/imagify/classes/WriteFile/WriteFileInterface.php
@@ -0,0 +1,68 @@
+
+ Geoffrey Crofte
+ Sébastien Decamme
+ Julio Potier
+ Caspar Hübinger
+ Remy Perona
+ Grégory Viguier
+
+Interface Design:
+ Matthieu Bousendorfer
+ Geoffrey Crofte
+
+Translation:
+ Alice Orrù (Italian & Spanish)
+ Caspar Hübinger (German)
+ Lucy Beer (English)
diff --git a/wp-content/plugins/imagify/imagify.php b/wp-content/plugins/imagify/imagify.php
new file mode 100644
index 00000000..9fe07561
--- /dev/null
+++ b/wp-content/plugins/imagify/imagify.php
@@ -0,0 +1,180 @@
+ IMAGIFY_PATH,
+ )
+ );
+
+ $plugin->init();
+}
+
+/**
+ * Check if Imagify is activated on the network.
+ *
+ * @since 1.0
+ *
+ * return bool True if Imagify is activated on the network.
+ */
+function imagify_is_active_for_network() {
+ static $is;
+
+ if ( isset( $is ) ) {
+ return $is;
+ }
+
+ if ( ! is_multisite() ) {
+ $is = false;
+ return $is;
+ }
+
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+
+ $is = is_plugin_active_for_network( plugin_basename( IMAGIFY_FILE ) );
+
+ return $is;
+}
+
+/**
+ * Check for WordPress and PHP version.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @return bool True if WP and PHP versions are OK.
+ */
+function imagify_pass_requirements() {
+ static $check;
+
+ if ( isset( $check ) ) {
+ return $check;
+ }
+
+ require_once IMAGIFY_PATH . 'inc/classes/class-imagify-requirements-check.php';
+
+ $requirement_checks = new Imagify_Requirements_Check(
+ array(
+ 'plugin_name' => 'Imagify',
+ 'plugin_file' => IMAGIFY_FILE,
+ 'plugin_version' => IMAGIFY_VERSION,
+ 'wp_version' => '4.0',
+ 'php_version' => '5.4',
+ )
+ );
+
+ $check = $requirement_checks->check();
+
+ return $check;
+}
+
+/**
+ * Load plugin translations.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ */
+function imagify_load_translations() {
+ static $done = false;
+
+ if ( $done ) {
+ return;
+ }
+
+ $done = true;
+
+ load_plugin_textdomain( 'imagify', false, dirname( plugin_basename( IMAGIFY_FILE ) ) . '/languages/' );
+}
+
+register_activation_hook( IMAGIFY_FILE, 'imagify_set_activation' );
+/**
+ * Set a transient on plugin activation, it will be used later to trigger activation hooks after the plugin is loaded.
+ * The transient contains the ID of the user that activated the plugin.
+ *
+ * @since 1.9
+ * @see Imagify_Plugin->maybe_activate()
+ * @author Grégory Viguier
+ */
+function imagify_set_activation() {
+ if ( ! imagify_pass_requirements() ) {
+ return;
+ }
+
+ if ( imagify_is_active_for_network() ) {
+ set_site_transient( 'imagify_activation', get_current_user_id(), 30 );
+ } else {
+ set_transient( 'imagify_activation', get_current_user_id(), 30 );
+ }
+}
+
+register_deactivation_hook( IMAGIFY_FILE, 'imagify_deactivation' );
+/**
+ * Trigger a hook on plugin deactivation.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ */
+function imagify_deactivation() {
+ if ( ! imagify_pass_requirements() ) {
+ return;
+ }
+
+ /**
+ * Imagify deactivation.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ */
+ do_action( 'imagify_deactivation' );
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/3rd-party.php b/wp-content/plugins/imagify/inc/3rd-party/3rd-party.php
new file mode 100644
index 00000000..d80909c3
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/3rd-party.php
@@ -0,0 +1,25 @@
+= 0 ) {
+ // A new version that removes a punlic method.
+ return false;
+ }
+
+ return true;
+ }
+
+ if ( function_exists( 'as3cf_pro_init' ) ) {
+ // WP Offload S3 Pro.
+ $version = ! empty( $GLOBALS['aws_meta']['amazon-s3-and-cloudfront-pro']['version'] ) ? $GLOBALS['aws_meta']['amazon-s3-and-cloudfront-pro']['version'] : false;
+
+ if ( ! $version ) {
+ return false;
+ }
+
+ if ( ! function_exists( 'amazon_web_services_init' ) && version_compare( $version, '1.6' ) < 0 ) {
+ // Old version, plugin Amazon Web Services is required.
+ return false;
+ }
+
+ if ( version_compare( $version, '2.3' ) >= 0 ) {
+ // A new version that removes a punlic method.
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+if ( imagify_load_as3cf_compat() ) :
+
+ class_alias( '\\Imagify\\ThirdParty\\AS3CF\\Main', '\\Imagify_AS3CF' );
+
+ add_action( 'imagify_loaded', [ \Imagify\ThirdParty\AS3CF\Main::get_instance(), 'init' ], 1 );
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php b/wp-content/plugins/imagify/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php
new file mode 100644
index 00000000..d834870e
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php
@@ -0,0 +1,513 @@
+id = (int) $media_id;
+ $this->filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Tell if the CDN is ready (not necessarily reachable).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_ready() {
+ global $as3cf;
+ static $is;
+
+ if ( ! isset( $is ) ) {
+ $is = $as3cf && $as3cf->is_plugin_setup();
+ }
+
+ return $is;
+ }
+
+ /**
+ * Tell if the media is on the CDN.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function media_is_on_cdn() {
+ return (bool) $this->get_cdn_info();
+ }
+
+ /**
+ * Get files from the CDN.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $file_paths A list of file paths.
+ * @return bool|\WP_Error True on success. A \WP_error object on failure.
+ */
+ public function get_files_from_cdn( $file_paths ) {
+ global $as3cf;
+
+ if ( ! $this->is_ready() ) {
+ return new \WP_Error( 'not_ready', __( 'CDN is not set up.', 'imagify' ) );
+ }
+
+ $cdn_info = $this->get_cdn_info();
+
+ if ( ! $cdn_info ) {
+ // The media is not on the CDN.
+ return new \WP_Error( 'not_on_cdn', __( 'This media could not be found on the CDN.', 'imagify' ) );
+ }
+
+ $directory = $this->filesystem->dir_path( $cdn_info['key'] );
+ $directory = $this->filesystem->is_root( $directory ) ? '' : $directory;
+ $new_method = method_exists( $as3cf->plugin_compat, 'copy_s3_file_to_server' );
+ $errors = [];
+
+ foreach ( $file_paths as $file_path ) {
+ $cdn_info['key'] = $directory . $this->filesystem->file_name( $file_path );
+
+ // Retrieve file from the CDN.
+ if ( $new_method ) {
+ $as3cf->plugin_compat->copy_s3_file_to_server( $cdn_info, $file_path );
+ } else {
+ $as3cf->plugin_compat->copy_provider_file_to_server( $cdn_info, $file_path );
+ }
+
+ if ( ! $this->filesystem->exists( $file_path ) ) {
+ $errors[] = $file_path;
+ }
+ }
+
+ if ( $errors ) {
+ $nbr_errors = count( $errors );
+ $errors_txt = array_map( [ $this->filesystem, 'make_path_relative' ], $errors );
+ $errors_txt = wp_sprintf_l( '%l', $errors_txt );
+
+ return new \WP_Error(
+ 'not_retrieved',
+ sprintf(
+ /* translators: %s is a list of file paths. */
+ _n( 'The following file could not be retrieved from the CDN: %s.', 'The following files could not be retrieved from the CDN: %s.', $nbr_errors, 'imagify' ),
+ $errors_txt
+ ),
+ $errors
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove files from the CDN.
+ * Don't use this to empty a folder.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $file_paths A list of file paths. Those paths are not necessary absolute, and can be also file names.
+ * @return bool|\WP_Error True on success. A \WP_error object on failure.
+ */
+ public function remove_files_from_cdn( $file_paths ) {
+ global $as3cf;
+
+ if ( ! $this->is_ready() ) {
+ return new \WP_Error( 'not_ready', __( 'CDN is not set up.', 'imagify' ) );
+ }
+
+ $cdn_info = $this->get_cdn_info();
+
+ if ( ! $cdn_info ) {
+ // The media is not on the CDN.
+ return new \WP_Error( 'not_on_cdn', __( 'This media could not be found on the CDN.', 'imagify' ) );
+ }
+
+ $directory = $this->filesystem->dir_path( $cdn_info['key'] );
+ $directory = $this->filesystem->is_root( $directory ) ? '' : $directory;
+
+ if ( method_exists( $as3cf, 'get_s3object_region' ) ) {
+ $region = $as3cf->get_s3object_region( $cdn_info );
+ } else {
+ $region = $as3cf->get_provider_object_region( $cdn_info );
+ }
+
+ if ( is_wp_error( $region ) ) {
+ $region = '';
+ }
+
+ $to_remove = [];
+
+ foreach ( $file_paths as $file_path ) {
+ $to_remove[] = [
+ 'Key' => $directory . $this->filesystem->file_name( $file_path ),
+ ];
+ }
+
+ if ( method_exists( $as3cf, 'delete_s3_objects' ) ) {
+ $result = $as3cf->delete_s3_objects( $region, $cdn_info['bucket'], $to_remove, false, false, false );
+ } else {
+ $result = $as3cf->delete_objects( $region, $cdn_info['bucket'], $to_remove, false, false, false );
+ }
+
+ if ( is_wp_error( $result ) ) {
+ return new \WP_Error( 'deletion_failed', __( 'File(s) could not be removed from the CDN.', 'imagify' ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * Send all files from a media to the CDN.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $is_new_upload Tell if the current media is a new upload. If not, it means it's a media being regenerated, restored, etc.
+ * @return bool|\WP_Error True/False if sent or not. A \WP_error object on failure.
+ */
+ public function send_to_cdn( $is_new_upload ) {
+ global $as3cf;
+
+ if ( ! $this->is_ready() ) {
+ return new \WP_Error( 'not_ready', __( 'CDN is not set up.', 'imagify' ) );
+ }
+
+ if ( ! $this->can_send_to_cdn( $is_new_upload ) ) {
+ return false;
+ }
+
+ // Retrieve the missing files from the CDN: we must send all of them at once, even those that have not been modified.
+ $file_paths = \AS3CF_Utils::get_attachment_file_paths( $this->id, false );
+
+ if ( $file_paths ) {
+ foreach ( $file_paths as $size => $file_path ) {
+ if ( $this->filesystem->exists( $file_path ) ) {
+ // Keep only the files that don't exist.
+ unset( $file_paths[ $size ] );
+ }
+ }
+ }
+
+ if ( $file_paths ) {
+ $result = $this->get_files_from_cdn( $file_paths );
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+ }
+
+ $remove_local_files = $this->should_delete_files( $is_new_upload );
+
+ if ( ! $is_new_upload ) {
+ if ( $remove_local_files ) {
+ // Force files deletion when not a new media.
+ add_filter( 'as3cf_get_setting', [ $this, 'force_local_file_removal_setting' ], 100, 2 );
+ add_filter( 'as3cf_setting_remove-local-file', 'imagify_return_true', 100 );
+ } else {
+ // Force to keep the files when not a new media.
+ add_filter( 'as3cf_get_setting', [ $this, 'force_local_file_keep_setting' ], 100, 2 );
+ add_filter( 'as3cf_setting_remove-local-file', 'imagify_return_false', 100 );
+ }
+ }
+
+ if ( method_exists( $as3cf, 'upload_attachment_to_s3' ) ) {
+ $result = $as3cf->upload_attachment_to_s3( $this->id, null, null, false, $remove_local_files );
+ } else {
+ $result = $as3cf->upload_attachment( $this->id, null, null, false, $remove_local_files );
+ }
+
+ if ( ! $is_new_upload ) {
+ if ( $remove_local_files ) {
+ remove_filter( 'as3cf_get_setting', [ $this, 'force_local_file_removal_setting' ], 100 );
+ remove_filter( 'as3cf_setting_remove-local-file', 'imagify_return_true', 100 );
+ } else {
+ remove_filter( 'as3cf_get_setting', [ $this, 'force_local_file_keep_setting' ], 100 );
+ remove_filter( 'as3cf_setting_remove-local-file', 'imagify_return_false', 100 );
+ }
+ }
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get a file URL.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_name Name of the file. Leave empty for the full size file.
+ * @return string URL to the file.
+ */
+ public function get_file_url( $file_name = false ) {
+ $file_url = wp_get_attachment_url( $this->id );
+
+ if ( $file_name ) {
+ // It's not the full size.
+ $file_url = $this->filesystem->dir_path( $file_url ) . $file_name;
+ }
+
+ return $file_url;
+ }
+
+ /**
+ * Get a file path.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_name Name of the file. Leave empty for the full size file. Use 'original' to get the path to the original file.
+ * @return string Path to the file.
+ */
+ public function get_file_path( $file_name = false ) {
+ if ( ! $file_name ) {
+ // Full size.
+ return get_attached_file( $this->id, true );
+ }
+
+ if ( 'original' === $file_name ) {
+ // Original file.
+ if ( $this->is_wp_53() ) {
+ // `wp_get_original_image_path()` may return false.
+ $file_path = wp_get_original_image_path( $this->id );
+ } else {
+ $file_path = false;
+ }
+
+ if ( ! $file_path ) {
+ $file_path = get_attached_file( $this->id, true );
+ }
+
+ return $file_path;
+ }
+
+ // Thumbnail.
+ $file_path = get_attached_file( $this->id, true );
+ $file_path = $this->filesystem->dir_path( $file_path ) . $file_name;
+
+ return $file_path;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** INTERNAL TOOLS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Filter the CDN setting 'remove-local-file': this is used to force deletion when the media is not a new one.
+ * Caution to not use $this->should_delete_files() when this filter is used!
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $setting The setting value.
+ * @param string $key The setting name.
+ * @return bool
+ */
+ public function force_local_file_removal_setting( $setting, $key ) {
+ if ( 'remove-local-file' === $key ) {
+ return true;
+ }
+
+ return $setting;
+ }
+
+ /**
+ * Filter the CDN setting 'remove-local-file': this is used to force not-deletion when the media is not a new one.
+ * Caution to not use $this->should_delete_files() when this filter is used!
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $setting The setting value.
+ * @param string $key The setting name.
+ * @return bool
+ */
+ public function force_local_file_keep_setting( $setting, $key ) {
+ if ( 'remove-local-file' === $key ) {
+ return false;
+ }
+
+ return $setting;
+ }
+
+ /**
+ * Tell if a media is stored on the CDN.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return array|bool The CDN info on success. False if the media is not on the CDN.
+ */
+ protected function get_cdn_info() {
+ global $as3cf;
+
+ if ( ! $as3cf ) {
+ return false;
+ }
+
+ if ( method_exists( $as3cf, 'get_attachment_s3_info' ) ) {
+ return $as3cf->get_attachment_s3_info( $this->id );
+ }
+
+ return $as3cf->get_attachment_provider_info( $this->id );
+ }
+
+ /**
+ * Tell if a media can (and should) be sent to the CDN.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param bool $is_new_upload Tell if the current media is a new upload. If not, it means it's a media being regenerated, restored, etc.
+ * @return bool
+ */
+ protected function can_send_to_cdn( $is_new_upload ) {
+ global $as3cf;
+ static $can = [];
+ static $cdn_setting;
+
+ if ( isset( $can[ $this->id ] ) ) {
+ return $can[ $this->id ];
+ }
+
+ if ( ! $this->is_ready() ) {
+ $can[ $this->id ] = false;
+ return $can[ $this->id ];
+ }
+
+ if ( ! isset( $cdn_setting ) ) {
+ $cdn_setting = $as3cf && $as3cf->get_setting( 'copy-to-s3' );
+ }
+
+ // The CDN is set up: test if the media is on it.
+ $can[ $this->id ] = $this->media_is_on_cdn();
+
+ if ( $can[ $this->id ] && $is_new_upload ) {
+ // Use the CDN setting to tell if we're allowed to send the files (should be true since it's a new upload and it's already there).
+ $can[ $this->id ] = $cdn_setting;
+ }
+
+ /**
+ * Tell if a media can (and should) be sent to the CDN.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $can True if the media can be sent. False otherwize.
+ * @param PushCDNInterface $cdn The CDN instance.
+ * @param bool $cdn_setting CDN setting that tells if a new media can be sent to the CDN.
+ * @param bool $is_new_upload Tell if the current media is a new upload. If not, it means it's a media being regenerated, restored, etc.
+ */
+ $can[ $this->id ] = (bool) apply_filters( 'imagify_can_send_to_cdn', $can[ $this->id ], $this, $cdn_setting, $is_new_upload );
+
+ return $can[ $this->id ];
+ }
+
+ /**
+ * Tell if the files should be deleted after optimization.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param bool $is_new_upload Tell if the current media is a new upload. If not, it means it's a media being regenerated, restored, etc.
+ * @return bool
+ */
+ protected function should_delete_files( $is_new_upload ) {
+ global $as3cf;
+
+ if ( $is_new_upload ) {
+ return (bool) $as3cf->get_setting( 'remove-local-file' );
+ }
+
+ // If the attachment has a 'filesize' metadata, that means the local files are meant to be deleted.
+ return (bool) get_post_meta( $this->id, 'wpos3_filesize_total', true );
+ }
+
+ /**
+ * Tell if weâre playing in WP 5.3âs garden.
+ *
+ * @since 1.9.8
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ protected function is_wp_53() {
+ if ( isset( $this->is_wp53 ) ) {
+ return $this->is_wp53;
+ }
+
+ $this->is_wp53 = function_exists( 'wp_get_original_image_path' );
+
+ return $this->is_wp53;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/amazon-s3-and-cloudfront/classes/Main.php b/wp-content/plugins/imagify/inc/3rd-party/amazon-s3-and-cloudfront/classes/Main.php
new file mode 100644
index 00000000..ca8da4a2
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/amazon-s3-and-cloudfront/classes/Main.php
@@ -0,0 +1,631 @@
+filesystem = \Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Launch the hooks.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ */
+ public function init() {
+ static $done = false;
+
+ if ( $done ) {
+ return;
+ }
+ $done = true;
+
+ /**
+ * Webp images to display with a tag.
+ */
+ add_action( 'as3cf_init', [ $this, 'store_s3_settings' ] );
+ add_filter( 'imagify_webp_picture_process_image', [ $this, 'picture_tag_webp_image' ] );
+
+ /**
+ * Register CDN.
+ */
+ add_filter( 'imagify_cdn', [ $this, 'register_cdn' ], 8, 3 );
+
+ /**
+ * Optimization process.
+ */
+ add_filter( 'imagify_before_optimize_size', [ $this, 'maybe_copy_file_from_cdn_before_optimization' ], 8, 6 );
+ add_action( 'imagify_after_optimize', [ $this, 'maybe_send_media_to_cdn_after_optimization' ], 8, 2 );
+
+ /**
+ * Restoration process.
+ */
+ add_action( 'imagify_after_restore_media', [ $this, 'maybe_send_media_to_cdn_after_restore' ], 8, 4 );
+
+ /**
+ * Webp support.
+ */
+ add_filter( 'as3cf_attachment_file_paths', [ $this, 'add_webp_images_to_attachment' ], 8, 3 );
+ add_filter( 'mime_types', [ $this, 'add_webp_support' ] );
+
+ /**
+ * Redirections.
+ */
+ add_filter( 'imagify_redirect_to', [ $this, 'redirect_referrer' ] );
+
+ /**
+ * Stats.
+ */
+ add_filter( 'imagify_total_attachment_filesize', [ $this, 'add_stats_for_s3_files' ], 8, 4 );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION PROCESS ==================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * On AS3CF init, store its settings.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param \Amazon_S3_And_CloudFront $as3cf AS3CFâs main instance.
+ */
+ public function store_s3_settings( $as3cf ) {
+ if ( method_exists( $as3cf, 'get_settings' ) ) {
+ $this->store_s3_settings = (array) $as3cf->get_settings();
+ }
+ }
+
+ /**
+ * Webp images to display with a tag.
+ *
+ * @since 1.9
+ * @see \Imagify\Webp\Picture\Display->process_image()
+ * @author Grégory Viguier
+ *
+ * @param array $data An array of data for this image.
+ * @return array
+ */
+ public function picture_tag_webp_image( $data ) {
+ global $wpdb;
+
+ if ( ! empty( $data['src']['webp_path'] ) ) {
+ // The file is local.
+ return $data;
+ }
+
+ $match = $this->is_s3_url( $data['src']['url'] );
+
+ if ( ! $match ) {
+ // The file is not on S3.
+ return $data;
+ }
+
+ // Get the image ID.
+ $post_id = (int) $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s",
+ // We use only year/month + filename, we should not have any subdir between them for the main file.
+ $match['year_month'] . $match['filename']
+ )
+ );
+
+ if ( $post_id <= 0 ) {
+ // Not in the database.
+ return $data;
+ }
+
+ $s3_info = get_post_meta( $post_id, 'amazonS3_info', true );
+ $imagify_data = get_post_meta( $post_id, '_imagify_data', true );
+
+ if ( ! $s3_info || ! $imagify_data ) {
+ return $data;
+ }
+
+ $webp_size_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::WEBP_SUFFIX' );
+ $webp_size_name = 'full' . $webp_size_suffix;
+
+ if ( ! empty( $imagify_data['sizes'][ $webp_size_name ]['success'] ) ) {
+ // We have a webp image.
+ $data['src']['webp_exists'] = true;
+ }
+
+ if ( empty( $data['srcset'] ) ) {
+ return $data;
+ }
+
+ $meta_data = get_post_meta( $post_id, '_wp_attachment_metadata', true );
+
+ if ( empty( $meta_data['sizes'] ) ) {
+ return $data;
+ }
+
+ // Ease the search for corresponding file name.
+ $size_files = [];
+
+ foreach ( $meta_data['sizes'] as $size_name => $size_data ) {
+ $size_files[ $size_data['file'] ] = $size_name;
+ }
+
+ // Look for a corresponding size name.
+ foreach ( $data['srcset'] as $i => $srcset_data ) {
+ if ( empty( $srcset_data['webp_url'] ) ) {
+ // Not a supported image format.
+ continue;
+ }
+ if ( ! empty( $srcset_data['webp_path'] ) ) {
+ // The file is local.
+ continue;
+ }
+
+ $match = $this->is_s3_url( $srcset_data['url'] );
+
+ if ( ! $match ) {
+ // Not on S3.
+ continue;
+ }
+
+ // Try with no subdirs.
+ $filename = $match['filename'];
+
+ if ( isset( $size_files[ $filename ] ) ) {
+ $size_name = $size_files[ $filename ];
+ } else {
+ // Try with subdirs.
+ $filename = $match['subdirs'] . $match['filename'];
+
+ if ( isset( $size_files[ $filename ] ) ) {
+ $size_name = $size_files[ $filename ];
+ } elseif ( preg_match( '@/\d+/$@', $match['subdirs'] ) ) {
+ // Last try: the subdirs may contain the S3 versioning. If not the case, we can still build a pyramid with this code.
+ $filename = preg_replace( '@/\d+/$@', '/', $match['subdirs'] ) . $match['filename'];
+
+ if ( isset( $size_files[ $filename ] ) ) {
+ $size_name = $size_files[ $filename ];
+ } else {
+ continue;
+ }
+ }
+ }
+
+ $webp_size_name = $size_name . $webp_size_suffix;
+
+ if ( ! empty( $imagify_data['sizes'][ $webp_size_name ]['success'] ) ) {
+ // We have a webp image.
+ $data['srcset'][ $i ]['webp_exists'] = true;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * The CDN to use for this media.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool|PushCDNInterface $cdn A PushCDNInterface instance. False if no CDN is used.
+ * @param int $media_id The media ID.
+ * @param ContextInterface $context The context object.
+ */
+ public function register_cdn( $cdn, $media_id, $context ) {
+ if ( 'wp' !== $context->get_name() ) {
+ return $cdn;
+ }
+ if ( $cdn instanceof PushCDNInterface ) {
+ return $cdn;
+ }
+
+ return new CDN( $media_id );
+ }
+
+ /**
+ * Before performing a file optimization, download the file from the CDN if it is missing.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param null|\WP_Error $response Null by default.
+ * @param ProcessInterface $process The optimization process instance.
+ * @param File $file The file instance. If $webp is true, $file references the non-webp file.
+ * @param string $thumb_size The media size.
+ * @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
+ * @param bool $webp The image will be converted to webp.
+ * @return null|\WP_Error Null. A \WP_Error object on error.
+ */
+ public function maybe_copy_file_from_cdn_before_optimization( $response, $process, $file, $thumb_size, $optimization_level, $webp ) {
+ if ( is_wp_error( $response ) || 'wp' !== $process->get_media()->get_context() ) {
+ return $response;
+ }
+
+ $media = $process->get_media();
+ $cdn = $media->get_cdn();
+
+ if ( ! $cdn instanceof CDN ) {
+ return $response;
+ }
+
+ if ( $this->filesystem->exists( $file->get_path() ) ) {
+ return $response;
+ }
+
+ // Get files from the CDN.
+ $result = $cdn->get_files_from_cdn( [ $file->get_path() ] );
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ return $response;
+ }
+
+ /**
+ * After performing a media optimization:
+ * - Save some data,
+ * - Upload the files to the CDN,
+ * - Maybe delete them from the server.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process.
+ * @param array $item The item being processed.
+ */
+ public function maybe_send_media_to_cdn_after_optimization( $process, $item ) {
+ if ( 'wp' !== $process->get_media()->get_context() ) {
+ return;
+ }
+
+ $media = $process->get_media();
+ $cdn = $media->get_cdn();
+
+ if ( ! $cdn instanceof CDN ) {
+ return;
+ }
+
+ $cdn->send_to_cdn( ! empty( $item['data']['is_new_upload'] ) );
+ }
+
+ /**
+ * After restoring a media:
+ * - Save some data,
+ * - Upload the files to the CDN,
+ * - Maybe delete webp files from the CDN.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process.
+ * @param bool|WP_Error $response The result of the operation: true on success, a WP_Error object on failure.
+ * @param array $files The list of files, before restoring them.
+ * @param array $data The optimization data, before deleting it.
+ */
+ public function maybe_send_media_to_cdn_after_restore( $process, $response, $files, $data ) {
+ if ( 'wp' !== $process->get_media()->get_context() ) {
+ return;
+ }
+
+ $media = $process->get_media();
+ $cdn = $media->get_cdn();
+
+ if ( ! $cdn instanceof CDN ) {
+ return;
+ }
+
+ if ( is_wp_error( $response ) ) {
+ $error_code = $response->get_error_code();
+
+ if ( 'copy_failed' === $error_code ) {
+ // No files have been restored.
+ return;
+ }
+
+ // No thumbnails left?
+ }
+
+ $cdn->send_to_cdn( false );
+
+ // Remove webp files from CDN.
+ $webp_files = [];
+
+ if ( $files ) {
+ // Get the paths to the webp files.
+ foreach ( $files as $size_name => $file ) {
+ $webp_size_name = $size_name . $process::WEBP_SUFFIX;
+
+ if ( empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
+ // This size has no webp version.
+ continue;
+ }
+
+ if ( 0 === strpos( $file['mime-type'], 'image/' ) ) {
+ $webp_file = new File( $file['path'] );
+
+ if ( ! $webp_file->is_webp() ) {
+ $webp_files[] = $webp_file->get_path_to_webp();
+ }
+ }
+ }
+ }
+
+ if ( $webp_files ) {
+ $cdn->remove_files_from_cdn( $webp_files );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION PROCESS ==================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Add the webp files to the list of files that the CDN must handle.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $paths A list of file paths, keyed by size name. 'file' for the full size. Includes a 'backup' size and a 'thumb' size.
+ * @param int $attachment_id The media ID.
+ * @param array $metadata The attachment meta data.
+ * @return array
+ */
+ public function add_webp_images_to_attachment( $paths, $attachment_id, $metadata ) {
+ if ( ! $paths ) {
+ // ¯\(°_o)/¯.
+ return $paths;
+ }
+
+ $process = imagify_get_optimization_process( $attachment_id, 'wp' );
+
+ if ( ! $process->is_valid() ) {
+ return $paths;
+ }
+
+ $media = $process->get_media();
+
+ if ( ! $media->is_image() ) {
+ return $paths;
+ }
+
+ // Use the optimization data (the files may not be on the server).
+ $data = $process->get_data()->get_optimization_data();
+
+ if ( empty( $data['sizes'] ) ) {
+ return $paths;
+ }
+
+ foreach ( $paths as $size_name => $file_path ) {
+ if ( 'thumb' === $size_name || 'backup' === $size_name || $process->is_size_webp( $size_name ) ) {
+ continue;
+ }
+
+ if ( 'file' === $size_name ) {
+ $size_name = 'full';
+ }
+
+ $webp_size_name = $size_name . $process::WEBP_SUFFIX;
+
+ if ( empty( $data['sizes'][ $webp_size_name ]['success'] ) ) {
+ // This size has no webp version.
+ continue;
+ }
+
+ $file = new File( $file_path );
+
+ if ( ! $file->is_webp() ) {
+ $paths[ $webp_size_name ] = $file->get_path_to_webp();
+ }
+ }
+
+ return $paths;
+ }
+
+ /**
+ * Add webp format to the list of allowed mime types.
+ *
+ * @since 1.9
+ * @access public
+ * @see get_allowed_mime_types()
+ * @author Grégory Viguier
+ *
+ * @param array $mime_types A list of mime types.
+ * @return array
+ */
+ public function add_webp_support( $mime_types ) {
+ $mime_types['webp'] = 'image/webp';
+ return $mime_types;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** VARIOUS HOOKS =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * After a non-ajax optimization, remove some unnecessary arguments from the referrer used for the redirection.
+ * Those arguments don't break anything, they're just not relevant and display obsolete admin notices.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $redirect The URL to redirect to.
+ * @return string
+ */
+ public function redirect_referrer( $redirect ) {
+ return remove_query_arg( [ 'as3cfpro-action', 'as3cf_id', 'errors', 'count' ], $redirect );
+ }
+
+ /**
+ * Provide the file sizes and the number of thumbnails for files that are only on S3.
+ *
+ * @since 1.6.7
+ * @author Grégory Viguier
+ *
+ * @param bool $size_and_count False by default.
+ * @param int $image_id The attachment ID.
+ * @param array $files An array of file paths with thumbnail sizes as keys.
+ * @param array $image_ids An array of all attachment IDs.
+ * @return bool|array False by default. Provide an array with the keys 'filesize' (containing the total filesize) and 'thumbnails' (containing the number of thumbnails).
+ */
+ public function add_stats_for_s3_files( $size_and_count, $image_id, $files, $image_ids ) {
+ static $data;
+
+ if ( is_array( $size_and_count ) ) {
+ return $size_and_count;
+ }
+
+ if ( $this->filesystem->exists( $files['full'] ) ) {
+ // If the full size is on the server, that probably means all files are on the server too.
+ return $size_and_count;
+ }
+
+ if ( ! isset( $data ) ) {
+ $data = \Imagify_DB::get_metas( [
+ // Get the filesizes.
+ 's3_filesize' => 'wpos3_filesize_total',
+ ], $image_ids );
+
+ $data = array_map( 'absint', $data['s3_filesize'] );
+ }
+
+ if ( empty( $data[ $image_id ] ) ) {
+ // The file is not on S3.
+ return $size_and_count;
+ }
+
+ // We can't take the disallowed sizes into account here.
+ return [
+ 'filesize' => (int) $data[ $image_id ],
+ 'thumbnails' => count( $files ) - 1,
+ ];
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if an URL is a S3 one.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $url The URL to test.
+ * @return array|bool {
+ * An array if an S3 URL. False otherwise.
+ *
+ * @type string $key Bucket key. Ex: subdir/wp-content/uploads/2019/02/13142432/foobar-480x510.jpg.
+ * @type string $year_month The uploads year/month folders. Ex: 2019/02/.
+ * @type string $subdirs Sub-directories between year/month folders and the filename.
+ * It can be the S3 versioning folder, any folder added by a plugin, or both.
+ * There is no way to know which one it is. Ex: foo/13142432/.
+ * @type string $filename The file name. Ex: foobar-480x510.jpg.
+ * }
+ */
+ public function is_s3_url( $url ) {
+ static $uploads_dir;
+ static $domain;
+
+ /**
+ * Tell if an URL is a S3 one.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param null|array|bool $is Null by default. Must return an array if an S3 URL, or false if not.
+ * @param string $url The URL to test.
+ */
+ $is = apply_filters( 'imagify_as3cf_is_s3_url', null, $url );
+
+ if ( false === $is ) {
+ return false;
+ }
+
+ if ( is_array( $is ) ) {
+ return imagify_merge_intersect( $is, [
+ 'key' => '',
+ 'year_month' => '',
+ 'subdirs' => '',
+ 'filename' => '',
+ ] );
+ }
+
+ if ( ! isset( $uploads_dir ) ) {
+ $uploads_dir = wp_parse_url( $this->filesystem->get_upload_baseurl() );
+ $uploads_dir = trim( $uploads_dir['path'], '/' ) . '/';
+ }
+
+ if ( ! isset( $domain ) ) {
+ if ( ! empty( $this->store_s3_settings['cloudfront'] ) ) {
+ $domain = sanitize_text_field( $this->store_s3_settings['cloudfront'] );
+ $domain = preg_replace( '@^(?:https?:)?//@', '//', $domain );
+ $domain = preg_quote( $domain, '@' );
+ } else {
+ $domain = 's3-.+\.amazonaws\.com/[^/]+/';
+ }
+ }
+
+ $pattern = '@^(?:https?:)?//' . $domain . '/(?' . $uploads_dir . '(?\d{4}/\d{2}/)?(?.+/)?(?[^/]+))$@i';
+
+ if ( ! preg_match( $pattern, $url, $match ) ) {
+ return false;
+ }
+
+ unset( $match[0] );
+
+ return array_merge( [
+ 'year_month' => '',
+ 'subdirs' => '',
+ ], $match );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/amp/amp.php b/wp-content/plugins/imagify/inc/3rd-party/amp/amp.php
new file mode 100644
index 00000000..4079d653
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/amp/amp.php
@@ -0,0 +1,23 @@
+ tags in AMP pages.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $allow True to allow the use of tags (default). False to prevent their use.
+ * @return bool
+ */
+ function imagify_amp_disable_picture_on_endpoint( $allow ) {
+ return $allow && ! is_amp_endpoint();
+ };
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/3rd-party/enable-media-replace/classes/Main.php b/wp-content/plugins/imagify/inc/3rd-party/enable-media-replace/classes/Main.php
new file mode 100644
index 00000000..ce14ea47
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/enable-media-replace/classes/Main.php
@@ -0,0 +1,162 @@
+media_id = $args['post_id'];
+ } else {
+ // Backward compatibility.
+ $this->media_id = (int) filter_input( INPUT_POST, 'ID' );
+ $this->media_id = max( 0, $this->media_id );
+
+ if ( ! $this->media_id ) {
+ return;
+ }
+ }
+
+ // Store the old backup file path.
+ $this->get_process();
+
+ if ( ! $this->process ) {
+ $this->media_id = 0;
+ return;
+ }
+
+ $this->old_backup_path = $this->process->get_media()->get_backup_path();
+
+ if ( ! $this->old_backup_path ) {
+ $this->media_id = 0;
+ return;
+ }
+
+ /**
+ * Keep track of existing webp files.
+ *
+ * Whether the user chooses to rename the files or not, we will need to delete the current webp files before creating new ones:
+ * - Rename the files: the old ones must be removed, they are useless now.
+ * - Do not rename the files: the thumbnails may still get new names because of the suffix containing the image dimensions, which may differ (for example when thumbnails are scaled, not cropped).
+ * In this last case, the thumbnails with the old dimensions are removed from the drive and from the WPâs post meta, so there is no need of keeping orphan webp files that would stay on the drive for ever, even after the attachment is deleted from WP.
+ */
+ foreach ( $this->process->get_media()->get_media_files() as $media_file ) {
+ $this->old_webp_paths[] = imagify_path_to_webp( $media_file['path'] );
+ }
+
+ // Delete the old backup file and old webp files.
+ add_action( 'imagify_before_auto_optimization', [ $this, 'delete_backup' ] );
+ add_action( 'imagify_not_optimized_attachment_updated', [ $this, 'delete_backup' ] );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** HOOKS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Delete previous backup file and webp files.
+ * This is done after the images have been already replaced by Enable Media Replace.
+ *
+ * @since 1.8.4
+ *
+ * @param int $media_id The attachment ID.
+ */
+ public function delete_backup( $media_id ) {
+ if ( ! $this->old_backup_path || ! $this->media_id || $media_id !== $this->media_id ) {
+ return;
+ }
+
+ $filesystem = Imagify_Filesystem::get_instance();
+
+ if ( $filesystem->exists( $this->old_backup_path ) ) {
+ // Delete old backup file.
+ $filesystem->delete( $this->old_backup_path );
+ $this->old_backup_path = false;
+ }
+
+ if ( ! empty( $this->old_webp_paths ) ) {
+ // Delete old webp files.
+ $this->old_webp_paths = array_filter( $this->old_webp_paths, [ $filesystem, 'exists' ] );
+ array_map( [ $filesystem, 'delete' ], $this->old_webp_paths );
+ $this->old_webp_paths = [];
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the optimization process corresponding to the current media.
+ *
+ * @since 1.9
+ *
+ * @return ProcessInterface|bool False if invalid.
+ */
+ protected function get_process() {
+ if ( isset( $this->process ) ) {
+ return $this->process;
+ }
+
+ $this->process = imagify_get_optimization_process( $this->media_id, 'wp' );
+
+ if ( ! $this->process->is_valid() ) {
+ $this->process = false;
+ }
+
+ return $this->process;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/enable-media-replace/enable-media-replace.php b/wp-content/plugins/imagify/inc/3rd-party/enable-media-replace/enable-media-replace.php
new file mode 100644
index 00000000..671992d9
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/enable-media-replace/enable-media-replace.php
@@ -0,0 +1,10 @@
+ true` argument.
+ *
+ * @since 1.6.13
+ * @author Grégory Viguier
+ */
+class Main {
+ use \Imagify\Traits\InstanceGetterTrait;
+
+ /**
+ * Class version.
+ *
+ * @var string
+ */
+ const VERSION = '1.1';
+
+ /**
+ * Set to true when the current query comes from Imagify.
+ *
+ * @var int
+ */
+ protected $is_imagify;
+
+ /**
+ * Launch the hooks.
+ *
+ * @since 1.6.13
+ * @author Grégory Viguier
+ */
+ public function init() {
+ add_action( 'parse_query', array( $this, 'maybe_remove_media_library_filter' ) );
+ add_action( 'posts_selection', array( $this, 'maybe_put_media_library_filter_back' ) );
+ }
+
+ /**
+ * Fires before the 'pre_get_posts' hook.
+ *
+ * @since 1.6.13
+ * @author Grégory Viguier
+ *
+ * @param object $wp_query The WP_Query instance (passed by reference).
+ */
+ public function maybe_remove_media_library_filter( $wp_query ) {
+ if ( ! empty( $wp_query->query_vars['is_imagify'] ) && class_exists( 'FrmProFileField' ) ) {
+ $this->is_imagify = true;
+ remove_action( 'pre_get_posts', 'FrmProFileField::filter_media_library', 99 );
+ } else {
+ $this->is_imagify = false;
+ }
+ }
+
+ /**
+ * Fires after the 'pre_get_posts' hook.
+ *
+ * @since 1.6.13
+ * @author Grégory Viguier
+ */
+ public function maybe_put_media_library_filter_back() {
+ if ( $this->is_imagify ) {
+ add_action( 'pre_get_posts', 'FrmProFileField::filter_media_library', 99 );
+ }
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/formidable-pro/formidable-pro.php b/wp-content/plugins/imagify/inc/3rd-party/formidable-pro/formidable-pro.php
new file mode 100644
index 00000000..e7cbdde2
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/formidable-pro/formidable-pro.php
@@ -0,0 +1,12 @@
+init();
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/3rd-party/hosting/flywheel.php b/wp-content/plugins/imagify/inc/3rd-party/hosting/flywheel.php
new file mode 100644
index 00000000..4b0c0784
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/hosting/flywheel.php
@@ -0,0 +1,34 @@
+get_upload_basedir( true );
+
+ if ( strpos( $upload_basedir, '/wp-content/' ) === false ) {
+ // Uh oooooh...
+ return $root_path;
+ }
+
+ $upload_basedir = explode( '/wp-content/', $upload_basedir );
+ $upload_basedir = reset( $upload_basedir );
+
+ return $upload_basedir . '/';
+ }
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/3rd-party/hosting/pressable.php b/wp-content/plugins/imagify/inc/3rd-party/hosting/pressable.php
new file mode 100644
index 00000000..02149b49
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/hosting/pressable.php
@@ -0,0 +1,24 @@
+ $path ) {
+ if ( false !== strpos( $url['path'], $path ) ) {
+ $paths = false;
+ break;
+ }
+ }
+
+ if ( $paths ) {
+ return $r;
+ }
+
+ // Randomize the User-Agent.
+ if ( ! isset( $user_agent ) ) {
+ $user_agent = wp_generate_password( 12, false );
+ /**
+ * Filter the User-Agent used for requests "to self".
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ *
+ * @param string $user_agent The User-Agent.
+ * @param array $r An array of HTTP request arguments.
+ * @param array $url The request URL, parsed.
+ */
+ $user_agent = apply_filters( 'imagify_user_agent_for_internal_requests', $user_agent, $r, $url );
+ }
+
+ $r['user-agent'] = $user_agent;
+
+ return $r;
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/hosting/wordpress-com.php b/wp-content/plugins/imagify/inc/3rd-party/hosting/wordpress-com.php
new file mode 100644
index 00000000..f9630aed
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/hosting/wordpress-com.php
@@ -0,0 +1,30 @@
+get_upload_basedir( true );
+
+ if ( strpos( $upload_basedir, '/wp-content/' ) === false ) {
+ // Uh oooooh...
+ return $root_path;
+ }
+
+ $upload_basedir = explode( '/wp-content/', $upload_basedir );
+ $upload_basedir = reset( $upload_basedir );
+
+ return $upload_basedir . '/';
+ }
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/3rd-party/hosting/wpengine.php b/wp-content/plugins/imagify/inc/3rd-party/hosting/wpengine.php
new file mode 100644
index 00000000..672503b3
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/hosting/wpengine.php
@@ -0,0 +1,21 @@
+prefix . 'ngg_pictures';
+ $data = [];
+ $images = $wpdb->get_results( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT DISTINCT picture.pid as id, picture.filename, idata.optimization_level, idata.status, idata.data
+ FROM $ngg_table as picture
+ LEFT JOIN $wpdb->ngg_imagify_data as idata
+ ON picture.pid = idata.pid
+ WHERE idata.pid IS NULL
+ OR idata.optimization_level != %d
+ OR idata.status = 'error'
+ LIMIT %d",
+ $optimization_level,
+ imagify_get_unoptimized_attachment_limit()
+ ), ARRAY_A );
+
+ if ( ! $images ) {
+ return [];
+ }
+
+ foreach ( $images as $image ) {
+ $id = absint( $image['id'] );
+ $file_path = $storage->get_image_abspath( $id );
+
+ if ( ! $file_path || ! $this->filesystem->exists( $file_path ) ) {
+ continue;
+ }
+
+ $attachment_data = maybe_unserialize( $image['data'] );
+ $attachment_error = '';
+
+ if ( isset( $attachment_data['sizes']['full']['error'] ) ) {
+ $attachment_error = $attachment_data['sizes']['full']['error'];
+ }
+
+ $attachment_error = trim( $attachment_error );
+ $attachment_status = $image['status'];
+ $attachment_optimization_level = $image['optimization_level'];
+ $attachment_backup_path = get_imagify_ngg_attachment_backup_path( $file_path );
+
+ // Don't try to re-optimize if the optimization level is still the same.
+ if ( $optimization_level === $attachment_optimization_level && is_string( $attachment_error ) ) {
+ continue;
+ }
+
+ // Don't try to re-optimize if there is no backup file.
+ if ( 'success' === $attachment_status && $optimization_level !== $attachment_optimization_level && ! $this->filesystem->exists( $attachment_backup_path ) ) {
+ continue;
+ }
+
+ // Don't try to re-optimize images already compressed.
+ if ( 'already_optimized' === $attachment_status && $attachment_optimization_level >= $optimization_level ) {
+ continue;
+ }
+
+ // Don't try to re-optimize images with an empty error message.
+ if ( 'error' === $attachment_status && empty( $attachment_error ) ) {
+ continue;
+ }
+
+ $data[ '_' . $id ] = esc_url( $storage->get_image_url( $id ) );
+ } // End foreach().
+
+ return $data;
+ }
+
+ /**
+ * Get ids of all optimized media without webp versions.
+ *
+ * @since 1.9
+ * @since 1.9.5 The method doesn't return the IDs directly anymore.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * @type array $ids A list of media IDs.
+ * @type array $errors {
+ * @type array $no_file_path A list of media IDs.
+ * @type array $no_backup A list of media IDs.
+ * }
+ * }
+ */
+ public function get_optimized_media_ids_without_webp() {
+ global $wpdb;
+
+ @set_time_limit( 0 );
+
+ $storage = \C_Gallery_Storage::get_instance();
+ $ngg_table = $wpdb->prefix . 'ngg_pictures';
+ $data_table = DB::get_instance()->get_table_name();
+ $webp_suffix = constant( imagify_get_optimization_process_class_name( 'ngg' ) . '::WEBP_SUFFIX' );
+ $files = $wpdb->get_col( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT ngg.pid
+ FROM $ngg_table as ngg
+ INNER JOIN $data_table AS data
+ ON ( ngg.pid = data.pid )
+ WHERE
+ ( data.status = 'success' OR data.status = 'already_optimized' )
+ AND data.data NOT LIKE %s
+ ORDER BY ngg.pid DESC",
+ '%' . $wpdb->esc_like( $webp_suffix . '";a:4:{s:7:"success";b:1;' ) . '%'
+ ) );
+
+ $wpdb->flush();
+ unset( $ngg_table, $data_table, $webp_suffix );
+
+ $data = [
+ 'ids' => [],
+ 'errors' => [
+ 'no_file_path' => [],
+ 'no_backup' => [],
+ ],
+ ];
+
+ if ( ! $files ) {
+ return $data;
+ }
+
+ foreach ( $files as $file_id ) {
+ $file_id = absint( $file_id );
+ $file_path = $storage->get_image_abspath( $file_id );
+
+ if ( ! $file_path ) {
+ // Problem.
+ $data['errors']['no_file_path'][] = $file_id;
+ continue;
+ }
+
+ $backup_path = get_imagify_ngg_attachment_backup_path( $file_path );
+
+ if ( ! $this->filesystem->exists( $backup_path ) ) {
+ // No backup, no webp.
+ $data['errors']['no_backup'][] = $file_id;
+ continue;
+ }
+
+ $data['ids'][] = $file_id;
+ } // End foreach().
+
+ return $data;
+ }
+
+ /**
+ * Tell if there are optimized media without webp versions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The number of media.
+ */
+ public function has_optimized_media_without_webp() {
+ global $wpdb;
+
+ $ngg_table = $wpdb->prefix . 'ngg_pictures';
+ $data_table = DB::get_instance()->get_table_name();
+ $webp_suffix = constant( imagify_get_optimization_process_class_name( 'ngg' ) . '::WEBP_SUFFIX' );
+
+ return (int) $wpdb->get_var( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT COUNT(ngg.pid)
+ FROM $ngg_table as ngg
+ INNER JOIN $data_table AS data
+ ON ( ngg.pid = data.pid )
+ WHERE
+ ( data.status = 'success' OR data.status = 'already_optimized' )
+ AND data.data NOT LIKE %s",
+ '%' . $wpdb->esc_like( $webp_suffix . '";a:4:{s:7:"success";b:1;' ) . '%'
+ ) );
+ }
+
+ /**
+ * Get the context data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * The formated data.
+ *
+ * @type string $count-optimized Number of media optimized.
+ * @type string $count-errors Number of media having an optimization error, with a link to the page listing the optimization errors.
+ * @type string $optimized-size Optimized filesize.
+ * @type string $original-size Original filesize.
+ * }
+ */
+ public function get_context_data() {
+ $total_saving_data = imagify_count_saving_data();
+ $data = [
+ 'count-optimized' => imagify_ngg_count_optimized_attachments(),
+ 'count-errors' => imagify_ngg_count_error_attachments(),
+ 'optimized-size' => $total_saving_data['optimized_size'],
+ 'original-size' => $total_saving_data['original_size'],
+ 'errors_url' => get_imagify_admin_url( 'folder-errors', $this->context ),
+ ];
+
+ return $this->format_context_data( $data );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Context/NGG.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Context/NGG.php
new file mode 100644
index 00000000..103543b4
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Context/NGG.php
@@ -0,0 +1,128 @@
+filter_capacity( $capacity, $describer );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/DB.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/DB.php
new file mode 100644
index 00000000..7f76be35
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/DB.php
@@ -0,0 +1,142 @@
+ '%d',
+ 'pid' => '%d',
+ 'optimization_level' => '%s',
+ 'status' => '%s',
+ 'data' => '%s',
+ );
+ }
+
+ /**
+ * Default column values.
+ *
+ * @since 1.5
+ * @access public
+ * @author Jonathan Buttigieg
+ *
+ * @return array
+ */
+ public function get_column_defaults() {
+ return array(
+ 'data_id' => 0,
+ 'pid' => 0,
+ 'optimization_level' => '',
+ 'status' => '',
+ 'data' => array(),
+ );
+ }
+
+ /**
+ * Get the query to create the table fields.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ protected function get_table_schema() {
+ return "
+ data_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ pid int(11) unsigned NOT NULL default 0,
+ optimization_level varchar(1) NOT NULL default '',
+ status varchar(30) NOT NULL default '',
+ data longtext NOT NULL default '',
+ PRIMARY KEY (data_id),
+ KEY pid (pid)";
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/DynamicThumbnails.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/DynamicThumbnails.php
new file mode 100644
index 00000000..5897d9ca
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/DynamicThumbnails.php
@@ -0,0 +1,133 @@
+pid ) ) {
+ // WUT?
+ return;
+ }
+
+ if ( empty( static::$sizes[ $image->pid ] ) ) {
+ static::$sizes[ $image->pid ] = [];
+ }
+
+ static::$sizes[ $image->pid ][] = $size;
+
+ if ( empty( static::$images[ $image->pid ] ) ) {
+ static::$images[ $image->pid ] = $image;
+ }
+
+ if ( $done ) {
+ return;
+ }
+
+ $done = true;
+
+ add_action( 'shutdown', [ $this, 'optimize' ], 555 ); // Must come before 666 (see Imagify_Abstract_Background_Process->init()).
+ }
+
+ /**
+ * Launch the optimizations.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function optimize() {
+ if ( empty( static::$sizes ) ) {
+ // ¯\(°_o)/¯
+ return;
+ }
+
+ foreach ( static::$sizes as $image_id => $sizes ) {
+ if ( empty( static::$images[ $image_id ] ) ) {
+ // ¯\(°_o)/¯
+ continue;
+ }
+
+ $sizes = array_filter( $sizes );
+
+ if ( empty( $sizes ) ) {
+ continue;
+ }
+
+ $process = imagify_get_optimization_process( static::$images[ $image_id ], 'ngg' );
+
+ if ( ! $process->is_valid() || ! $process->get_media()->is_supported() ) {
+ continue;
+ }
+
+ $data = $process->get_data();
+
+ if ( ! $data->is_optimized() ) {
+ // The main image is not optimized.
+ continue;
+ }
+
+ $sizes = array_unique( $sizes );
+
+ foreach ( $sizes as $i => $size ) {
+ $size_status = $data->get_size_data( $size, 'success' );
+
+ if ( $size_status ) {
+ // This thumbnail has already been processed.
+ unset( $sizes[ $i ] );
+ }
+ }
+
+ if ( empty( $sizes ) ) {
+ continue;
+ }
+
+ $optimization_level = $process->get_data()->get_optimization_level();
+ $args = [
+ 'hook_suffix' => 'optimize_generated_image',
+ ];
+
+ $process->optimize_sizes( $sizes, $optimization_level, $args );
+ }
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Main.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Main.php
new file mode 100644
index 00000000..701c0911
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Main.php
@@ -0,0 +1,137 @@
+get_wrapped_instance()->add_mixin( '\\Imagify\\ThirdParty\\NGG\\NGGStorage' );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php
new file mode 100644
index 00000000..aa70fe9d
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php
@@ -0,0 +1,543 @@
+image = \nggdb::find_image( (int) $id );
+ $id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
+ } elseif ( $id instanceof \nggImage ) {
+ $this->image = $id;
+ $id = (int) $id->pid;
+ } elseif ( is_object( $id ) ) {
+ $this->image = \nggdb::find_image( (int) $id->pid );
+ $id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
+ } else {
+ $id = 0;
+ }
+
+ if ( ! $id ) {
+ $this->image = null;
+
+ parent::__construct( 0 );
+ return;
+ }
+
+ parent::__construct( $id );
+
+ // NGG storage.
+ if ( ! empty( $this->image->_ngiw ) ) {
+ $this->storage = $this->image->_ngiw->get_storage()->object;
+ } else {
+ $this->storage = \C_Gallery_Storage::get_instance()->object;
+ }
+
+ // Load nggAdmin class.
+ $ngg_admin_functions_path = WP_PLUGIN_DIR . '/' . NGGFOLDER . '/products/photocrati_nextgen/modules/ngglegacy/admin/functions.php';
+
+ if ( ! class_exists( 'nggAdmin' ) && $this->filesystem->exists( $ngg_admin_functions_path ) ) {
+ require_once $ngg_admin_functions_path;
+ }
+ }
+
+ /**
+ * Tell if the given entry can be accepted in the constructor.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param mixed $id Whatever.
+ * @return bool
+ */
+ public static function constructor_accepts( $id ) {
+ if ( ! $id ) {
+ return false;
+ }
+
+ if ( is_numeric( $id ) || $id instanceof \nggImage ) {
+ return true;
+ }
+
+ return is_object( $id ) && ! empty( $id->pid ) && is_numeric( $id->pid );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** NGG SPECIFICS =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the NGG image object.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return \nggImage
+ */
+ public function get_ngg_image() {
+ return $this->image;
+ }
+
+ /**
+ * Get the NGG storage object.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return \C_Gallery_Storage
+ */
+ public function get_ngg_storage() {
+ return $this->storage;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** ORIGINAL FILE =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the original file path, even if the file doesn't exist.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_original_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_path( 'original' );
+ }
+
+ return ! empty( $this->image->imagePath ) ? $this->image->imagePath : false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** FULL SIZE FILE ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the URL of the mediaâs full size file.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file URL. False on failure.
+ */
+ public function get_fullsize_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_url();
+ }
+
+ return ! empty( $this->image->imageURL ) ? $this->image->imageURL : false;
+ }
+
+ /**
+ * Get the path to the mediaâs full size file, even if the file doesn't exist.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_fullsize_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_path();
+ }
+
+ return ! empty( $this->image->imagePath ) ? $this->image->imagePath : false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BACKUP FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the backup URL, even if the file doesn't exist.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file URL. False on failure.
+ */
+ public function get_backup_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return site_url( '/' . $this->filesystem->make_path_relative( $this->get_raw_backup_path() ) );
+ }
+
+ /**
+ * Get the backup file path, even if the file doesn't exist.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_backup_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return get_imagify_ngg_attachment_backup_path( $this->get_raw_original_path() );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** THUMBNAILS ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Create the media thumbnails.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True on success. A \WP_Error instance on failure.
+ */
+ public function generate_thumbnails() {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $image_data = $this->storage->_image_mapper->find( $this->get_id() ); // stdClass Object.
+
+ if ( ! $image_data ) {
+ // ¯\(°_o)/¯
+ return new \WP_Error( 'no_ngg_image', __( 'Image not found in NextGen Gallery data.', 'imagify' ) );
+ }
+
+ if ( empty( $image_data->meta_data['backup'] ) || ! is_array( $image_data->meta_data['backup'] ) ) {
+ $full_path = $this->storage->get_image_abspath( $image_data );
+
+ $image_data->meta_data['backup'] = [
+ 'filename' => $this->filesystem->file_name( $full_path ), // Yes, $full_path.
+ 'width' => $image_data->meta_data['width'], // Original image width.
+ 'height' => $image_data->meta_data['height'], // Original image height.
+ 'generated' => microtime(),
+ ];
+ }
+
+ $backup_path = $this->storage->get_image_abspath( $image_data, 'backup' );
+ $failed = [];
+
+ foreach ( $this->get_media_files() as $size_name => $size_data ) {
+ if ( 'full' === $size_name ) {
+ continue;
+ }
+
+ $params = $this->storage->get_image_size_params( $image_data, $size_name );
+ $thumbnail = @$this->storage->generate_image_clone( // Don't remove this @ or the sky will fall.
+ $backup_path,
+ $this->storage->get_image_abspath( $image_data, $size_name ),
+ $params
+ );
+
+ if ( ! $thumbnail ) {
+ // Failed.
+ $failed[] = $size_name;
+ unset( $image_data->meta_data[ $size_name ] );
+ continue;
+ }
+
+ $size_meta = [
+ 'width' => 0,
+ 'height' => 0,
+ 'filename' => \M_I18n::mb_basename( $thumbnail->fileName ),
+ 'generated' => microtime(),
+ ];
+
+ $dimensions = $this->filesystem->get_image_size( $thumbnail->fileName );
+
+ if ( $dimensions ) {
+ $size_meta['width'] = $dimensions['width'];
+ $size_meta['height'] = $dimensions['height'];
+ }
+
+ if ( isset( $params['crop_frame'] ) ) {
+ $size_meta['crop_frame'] = $params['crop_frame'];
+ }
+
+ $image_data->meta_data[ $size_name ] = $size_meta;
+ } // End foreach().
+
+ // Keep our property up to date.
+ $this->image->_ngiw->_cache['meta_data'] = $image_data->meta_data;
+ $this->image->_ngiw->_orig_image = $image_data;
+
+ $post_id = $this->storage->_image_mapper->save( $image_data );
+
+ if ( ! $post_id ) {
+ return new \WP_Error( 'meta_data_not_saved', __( 'Related NextGen Gallery data could not be saved.', 'imagify' ) );
+ }
+
+ if ( $failed ) {
+ return new \WP_Error(
+ 'thumbnail_restore_failed',
+ sprintf( _n( '%n thumbnail could not be restored.', '%n thumbnails could not be restored.', count( $failed ), 'imagify' ), count( $failed ) ),
+ [ 'failed_thumbnails' => $failed ]
+ );
+ }
+
+ return true;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** MEDIA DATA ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if the current media has the required data (the data containing the file paths and thumbnails).
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_required_media_data() {
+ static $sizes;
+
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( ! isset( $sizes ) ) {
+ $sizes = $this->get_media_files();
+ }
+
+ return $sizes && ! empty( $this->image->imagePath );
+ }
+
+ /**
+ * Get the list of the files of this media, including the full size file.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * An array with the size names as keys ('full' is used for the full size file), and arrays of data as values:
+ *
+ * @type string $size The size name.
+ * @type string $path Absolute path to the file.
+ * @type int $width The file width.
+ * @type int $height The file height.
+ * @type string $mime-type The file mime type.
+ * @type bool $disabled True if the size is disabled in the pluginâs settings.
+ * }
+ */
+ public function get_media_files() {
+ if ( ! $this->is_valid() ) {
+ return [];
+ }
+
+ $fullsize_path = $this->get_raw_fullsize_path();
+
+ if ( ! $fullsize_path ) {
+ return [];
+ }
+
+ $dimensions = $this->get_dimensions();
+ $all_sizes = [
+ 'full' => [
+ 'size' => 'full',
+ 'path' => $fullsize_path,
+ 'width' => $dimensions['width'],
+ 'height' => $dimensions['height'],
+ 'mime-type' => $this->get_mime_type(),
+ 'disabled' => false,
+ ],
+ ];
+
+ if ( ! $this->is_image() ) {
+ return $this->filter_media_files( $all_sizes );
+ }
+
+ // Remove common values (that have no value for us here, lol). Also remove 'full' and 'backup'.
+ $image_data = array_diff_key( $this->image->meta_data, [
+ 'full' => 1,
+ 'backup' => 1,
+ 'width' => 1,
+ 'height' => 1,
+ 'md5' => 1,
+ 'aperture' => 1,
+ 'credit' => 1,
+ 'camera' => 1,
+ 'caption' => 1,
+ 'created_timestamp' => 1,
+ 'copyright' => 1,
+ 'focal_length' => 1,
+ 'iso' => 1,
+ 'shutter_speed' => 1,
+ 'flash' => 1,
+ 'title' => 1,
+ 'keywords' => 1,
+ 'saved' => 1,
+ ] );
+
+ if ( ! $image_data ) {
+ return $this->filter_media_files( $all_sizes );
+ }
+
+ $ngg_data = $this->storage->_image_mapper->find( $this->get_id() );
+
+ foreach ( $image_data as $size => $size_data ) {
+ if ( ! isset( $size_data['width'], $size_data['height'], $size_data['filename'], $size_data['generated'] ) ) {
+ continue;
+ }
+
+ $file_type = (object) wp_check_filetype( $size_data['filename'], $this->get_allowed_mime_types() );
+
+ if ( ! $file_type->type ) {
+ continue;
+ }
+
+ $all_sizes[ $size ] = [
+ 'size' => $size,
+ 'path' => $this->storage->get_image_abspath( $ngg_data, $size ),
+ 'width' => (int) $size_data['width'],
+ 'height' => (int) $size_data['height'],
+ 'mime-type' => $file_type->type,
+ 'disabled' => false,
+ ];
+ }
+
+ return $this->filter_media_files( $all_sizes );
+ }
+
+ /**
+ * If the media is an image, get its width and height.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_dimensions() {
+ if ( ! $this->is_image() ) {
+ return [
+ 'width' => 0,
+ 'height' => 0,
+ ];
+ }
+
+ return [
+ 'width' => ! empty( $this->image->meta_data['width'] ) ? (int) $this->image->meta_data['width'] : 0,
+ 'height' => ! empty( $this->image->meta_data['height'] ) ? (int) $this->image->meta_data['height'] : 0,
+ ];
+ }
+
+ /**
+ * Update the media data dimensions.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $dimensions {
+ * An array containing width and height.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ */
+ protected function update_media_data_dimensions( $dimensions ) {
+ $changed = false;
+ $data = [
+ 'width' => $dimensions['width'],
+ 'height' => $dimensions['height'],
+ 'md5' => md5_file( $this->get_raw_fullsize_path() ),
+ ];
+
+ foreach ( $data as $k => $v ) {
+ if ( ! isset( $this->image->meta_data[ $k ] ) || $this->image->meta_data[ $k ] !== $v ) {
+ $this->image->meta_data[ $k ] = $v;
+ $changed = true;
+ }
+ }
+
+ if ( $changed ) {
+ \nggdb::update_image_meta( $this->id, $this->image->meta_data );
+ }
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/NGGStorage.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/NGGStorage.php
new file mode 100644
index 00000000..748e6441
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/NGGStorage.php
@@ -0,0 +1,106 @@
+{$gallery->id_field};
+ $images_id = \nggdb::get_ids_from_gallery( $gallery_id );
+
+ foreach ( $images_id as $pid ) {
+ $process = imagify_get_optimization_process( $pid, 'ngg' );
+
+ if ( $process->is_valid() && $process->get_data()->is_optimized() ) {
+ $process->get_data()->delete_optimization_data();
+ }
+ }
+
+ return $this->call_parent( 'delete_gallery', $gallery );
+ }
+
+ /**
+ * Generates a specific size for an image.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param int|object $image An image ID or NGG object.
+ * @param string $size An image size.
+ * @param array $params An array of parameters.
+ * @param bool $skip_defaults Whatever NGG does with default settings.
+ * @return bool|object An object on success. False on failure.
+ */
+ public function generate_image_size( $image, $size, $params = null, $skip_defaults = false ) {
+ // $image could be an object or an (int) image ID.
+ if ( is_numeric( $image ) ) {
+ $image = $this->object->_image_mapper->find( $image );
+ }
+
+ // If a user adds a watermark, rotates or resizes an image, we restore it.
+ // TO DO - waiting for a hook to be able to re-optimize the original size after restoring.
+ if ( isset( $image->pid ) && ( true === $params['watermark'] || ( isset( $params['rotation'] ) || isset( $params['flip'] ) ) || ( ! empty( $params['width'] ) || ! empty( $params['height'] ) ) ) ) {
+ $process = imagify_get_optimization_process( $image->pid, 'ngg' );
+
+ if ( $process->is_valid() && $process->get_data()->is_optimized() ) {
+ $process->get_data()->delete_optimization_data();
+ }
+ }
+
+ return $this->call_parent( 'generate_image_size', $image, $size, $params, $skip_defaults );
+ }
+
+ /**
+ * Recover image from backup.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param int|object $image An image ID or NGG object.
+ * @return string|bool Result code on success. False on failure.
+ */
+ public function recover_image( $image ) {
+ // $image could be an object or an (int) image ID.
+ if ( is_numeric( $image ) ) {
+ $image = $this->object->_image_mapper->find( $image );
+ }
+
+ if ( ! $image ) {
+ return false;
+ }
+
+ // Remove Imagify data.
+ if ( isset( $image->pid ) ) {
+ $process = imagify_get_optimization_process( $image->pid, 'ngg' );
+
+ if ( $process->is_valid() && $process->get_data()->is_optimized() ) {
+ $process->get_data()->delete_optimization_data();
+ }
+ }
+
+ return $this->call_parent( 'recover_image', $image );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php
new file mode 100644
index 00000000..1c938305
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php
@@ -0,0 +1,284 @@
+is_valid() ) {
+ return;
+ }
+
+ // This is required by MediaRowTrait.
+ $this->id = $this->get_media()->get_id();
+ }
+
+ /**
+ * Get the whole media optimization data.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array The data. See parent method for details.
+ */
+ public function get_optimization_data() {
+ if ( ! $this->is_valid() ) {
+ return $this->default_optimization_data;
+ }
+
+ $row = array_merge( $this->get_row_db_instance()->get_column_defaults(), $this->get_row() );
+ $data = $this->default_optimization_data;
+
+ $data['status'] = $row['status'];
+ $data['level'] = $row['optimization_level'];
+ $data['level'] = is_numeric( $data['level'] ) ? (int) $data['level'] : false;
+ $data['stats'] = [
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ ];
+
+ if ( ! empty( $row['data']['sizes'] ) && is_array( $row['data']['sizes'] ) ) {
+ $data['sizes'] = $row['data']['sizes'];
+ $data['sizes'] = array_filter( $data['sizes'], 'is_array' );
+ }
+
+ if ( empty( $data['sizes']['full'] ) ) {
+ if ( 'success' === $row['status'] ) {
+ $data['sizes']['full'] = [
+ 'success' => true,
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ ];
+ } elseif ( ! empty( $row['status'] ) ) {
+ $data['sizes']['full'] = [
+ 'success' => false,
+ 'error' => '',
+ ];
+ }
+ }
+
+ if ( empty( $data['sizes'] ) ) {
+ return $data;
+ }
+
+ foreach ( $data['sizes'] as $size_data ) {
+ // Cast.
+ if ( isset( $size_data['original_size'] ) ) {
+ $size_data['original_size'] = (int) $size_data['original_size'];
+ }
+ if ( isset( $size_data['optimized_size'] ) ) {
+ $size_data['optimized_size'] = (int) $size_data['optimized_size'];
+ }
+ if ( isset( $size_data['percent'] ) ) {
+ $size_data['percent'] = round( $size_data['percent'], 2 );
+ }
+ // Stats.
+ if ( ! empty( $size_data['original_size'] ) && ! empty( $size_data['optimized_size'] ) ) {
+ $data['stats']['original_size'] += $size_data['original_size'];
+ $data['stats']['optimized_size'] += $size_data['optimized_size'];
+ }
+ }
+
+ if ( $data['stats']['original_size'] && $data['stats']['optimized_size'] ) {
+ $data['stats']['percent'] = $data['stats']['original_size'] - $data['stats']['optimized_size'];
+ $data['stats']['percent'] = round( $data['stats']['percent'] / $data['stats']['original_size'] * 100, 2 );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Update the optimization data, level, and status for a size.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The size name.
+ * @param array $data The optimization data. See parent method for details.
+ */
+ public function update_size_optimization_data( $size, array $data ) {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ $old_data = array_merge( $this->get_reset_data(), $this->get_row() );
+
+ $old_data['data']['sizes'] = ! empty( $old_data['data']['sizes'] ) && is_array( $old_data['data']['sizes'] ) ? $old_data['data']['sizes'] : [];
+
+ if ( 'full' === $size ) {
+ /**
+ * Original file.
+ */
+ $old_data['optimization_level'] = $data['level'];
+ $old_data['status'] = $data['status'];
+ }
+
+ if ( ! $data['success'] ) {
+ /**
+ * Error.
+ */
+ $old_data['data']['sizes'][ $size ] = [
+ 'success' => false,
+ 'error' => $data['error'],
+ ];
+ } else {
+ /**
+ * Success.
+ */
+ $old_data['data']['sizes'][ $size ] = [
+ 'success' => true,
+ 'original_size' => $data['original_size'],
+ 'optimized_size' => $data['optimized_size'],
+ 'percent' => round( ( $data['original_size'] - $data['optimized_size'] ) / $data['original_size'] * 100, 2 ),
+ ];
+ }
+
+ $this->update_row( $old_data );
+ }
+
+ /**
+ * Delete the media optimization data, level, and status.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_optimization_data() {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ $this->delete_row();
+ }
+
+ /**
+ * Delete the optimization data for the given sizes.
+ * If all sizes are removed, all optimization data is deleted.
+ * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state.
+ *
+ * @since 1.9.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes A list of sizes to remove.
+ */
+ public function delete_sizes_optimization_data( array $sizes ) {
+ if ( ! $sizes || ! $this->is_valid() ) {
+ return;
+ }
+
+ $data = array_merge( $this->get_reset_data(), $this->get_row() );
+
+ $data['data']['sizes'] = ! empty( $data['data']['sizes'] ) && is_array( $data['data']['sizes'] ) ? $data['data']['sizes'] : [];
+
+ if ( ! $data['data']['sizes'] ) {
+ return;
+ }
+
+ $remaining_sizes_data = array_diff_key( $data['data']['sizes'], array_flip( $sizes ) );
+
+ if ( ! $remaining_sizes_data ) {
+ // All sizes have been removed: delete everything.
+ $this->delete_optimization_data();
+ return;
+ }
+
+ if ( count( $remaining_sizes_data ) === count( $data['data']['sizes'] ) ) {
+ // Nothing has been removed.
+ return;
+ }
+
+ $data['data']['sizes'] = $remaining_sizes_data;
+
+ $this->update_row( $data );
+ }
+
+ /**
+ * Get default values used to reset optimization data.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * The default values related to the optimization.
+ *
+ * @type string $optimization_level The optimization level.
+ * @type string $status The status: success, already_optimized, error.
+ * @type array $data Data related to the thumbnails.
+ * }
+ */
+ protected function get_reset_data() {
+ $db_instance = $this->get_row_db_instance();
+ $primary_key = $db_instance->get_primary_key();
+ $column_defaults = $db_instance->get_column_defaults();
+
+ return array_diff_key( $column_defaults, [
+ 'data_id' => 0,
+ $primary_key => 0,
+ ] );
+ }
+
+ /**
+ * Update the row.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $data The data to update.
+ */
+ public function update_row( $data ) {
+ if ( ! $this->db_class_name || $this->id <= 0 ) {
+ return;
+ }
+
+ $primary_key = $this->get_row_db_instance()->get_primary_key();
+ // This is needed in case the row doesn't exist yet.
+ $data[ $primary_key ] = $this->id;
+
+ $this->get_row_db_instance()->update( $this->id, $data );
+
+ $this->reset_row_cache();
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Optimization/Process/NGG.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Optimization/Process/NGG.php
new file mode 100644
index 00000000..a4a549ca
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/classes/Optimization/Process/NGG.php
@@ -0,0 +1,88 @@
+is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $media = $this->get_media();
+
+ if ( ! $media->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ $data = $this->get_data();
+
+ if ( ! $data->is_optimized() ) {
+ return new \WP_Error( 'media_not_optimized', __( 'This media is not optimized yet.', 'imagify' ) );
+ }
+
+ if ( ! $media->has_backup() ) {
+ return new \WP_Error( 'no_backup', __( 'This file has no backup file.', 'imagify' ) );
+ }
+
+ if ( ! $media->is_image() ) {
+ return new \WP_Error( 'media_not_an_image', __( 'This media is not an image.', 'imagify' ) );
+ }
+
+ return [];
+ }
+
+ /**
+ * Optimize missing thumbnail sizes.
+ * Since this context doesn't handle this feature, this will always return a \WP_Error object.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ public function optimize_missing_thumbnails() {
+ if ( ! $this->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ if ( ! $this->get_media()->is_supported() ) {
+ return new \WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
+ }
+
+ return new \WP_Error( 'no_sizes', __( 'No thumbnails seem to be missing.', 'imagify' ) );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/bulk.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/bulk.php
new file mode 100644
index 00000000..0226b3b3
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/bulk.php
@@ -0,0 +1,107 @@
+ 'library',
+ 'context' => 'ngg',
+ 'title' => __( 'NextGen Galleries', 'imagify' ),
+ /* translators: 1 is the opening of a link, 2 is the closing of this link. */
+ 'footer' => sprintf( __( 'You can also re-optimize your images more finely directly in each %1$sgallery%2$s.', 'imagify' ), '', ' ' ),
+ );
+
+ return $data;
+}
+
+add_filter( 'imagify_optimization_errors_url', 'imagify_ngg_optimization_errors_url', 10, 2 );
+/**
+ * Provide a URL to a page displaying optimization errors for the NGG context.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $url The URL.
+ * @param string $context The context.
+ * @return string
+ */
+function imagify_ngg_optimization_errors_url( $url, $context ) {
+ if ( 'ngg' === $context ) {
+ return admin_url( 'admin.php?page=nggallery-manage-gallery' );
+ }
+
+ return $url;
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/enqueue.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/enqueue.php
new file mode 100644
index 00000000..c62e6090
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/enqueue.php
@@ -0,0 +1,45 @@
+enqueue_style( 'admin' )->enqueue_script( 'library' );
+ return;
+ }
+
+ /**
+ * NGG Bulk Optimization.
+ */
+ $bulk_screen_id = imagify_get_ngg_bulk_screen_id();
+
+ if ( ! imagify_is_screen( $bulk_screen_id ) ) {
+ return;
+ }
+
+ $assets->remove_deferred_localization( 'bulk', 'imagifyBulk' );
+
+ $l10n = $assets->get_localization_data( 'bulk', [
+ 'bufferSizes' => [
+ 'ngg' => 4,
+ ],
+ ] );
+
+ /** This filter is documented in inc/functions/i18n.php */
+ $l10n['bufferSizes'] = apply_filters( 'imagify_bulk_buffer_sizes', $l10n['bufferSizes'] );
+
+ $assets->enqueue_assets( [ 'pricing-modal', 'bulk' ] )->localize( 'imagifyBulk', $l10n );
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/gallery.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/gallery.php
new file mode 100644
index 00000000..bcd71d36
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/admin/gallery.php
@@ -0,0 +1,64 @@
+get_capacity( 'bulk-optimize' );
+
+ add_submenu_page( NGGFOLDER, __( 'Bulk Optimization', 'imagify' ), __( 'Bulk Optimization', 'imagify' ), $capacity, imagify_get_ngg_bulk_screen_slug(), '_imagify_display_bulk_page' );
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/common/attachments.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/common/attachments.php
new file mode 100644
index 00000000..7bc33d6f
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/common/attachments.php
@@ -0,0 +1,329 @@
+pid ) ) {
+ $image_id = (int) $image->pid;
+ } else {
+ $image_id = 0;
+ }
+
+ if ( ! $image_id ) {
+ continue;
+ }
+
+ /**
+ * Allow to prevent automatic optimization for a specific NGG gallery image.
+ *
+ * @since 1.6.12
+ * @author Grégory Viguier
+ *
+ * @param bool $optimize True to optimize, false otherwise.
+ * @param int $image_id Image ID.
+ * @param int $gallery_id Gallery ID.
+ */
+ $optimize = apply_filters( 'imagify_auto_optimize_ngg_gallery_image', true, $image_id, $gallery_id );
+
+ if ( ! $optimize ) {
+ continue;
+ }
+
+ $process = imagify_get_optimization_process( $image, 'ngg' );
+
+ if ( ! $process->is_valid() ) {
+ continue;
+ }
+
+ if ( $process->get_data()->get_optimization_status() ) {
+ // Optimization already attempted.
+ continue;
+ }
+
+ $process->optimize();
+ }
+}
+
+add_filter( 'ngg_medialibrary_imported_image', '_imagify_ngg_media_library_imported_image_data', 10, 2 );
+/**
+ * Import Imagify data from a WordPress image to a new NGG image, and optimize the thumbnails.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param object $image A NGG image object.
+ * @param object $attachment An attachment object.
+ * @return object
+ */
+function _imagify_ngg_media_library_imported_image_data( $image, $attachment ) {
+ $wp_process = imagify_get_optimization_process( $attachment->ID, 'wp' );
+
+ if ( ! $wp_process->is_valid() || ! $wp_process->get_media()->is_supported() ) {
+ return $image;
+ }
+
+ $wp_data = $wp_process->get_data();
+
+ if ( ! $wp_data->is_optimized() ) {
+ // The main image is not optimized.
+ return $image;
+ }
+
+ // Copy the full size data.
+ $wp_full_size_data = $wp_data->get_size_data();
+ $optimization_level = $wp_data->get_optimization_level();
+
+ NGG\DB::get_instance()->update( $image->pid, [
+ 'pid' => $image->pid,
+ 'optimization_level' => $optimization_level,
+ 'status' => $wp_data->get_optimization_status(),
+ 'data' => [
+ 'sizes' => [
+ 'full' => $wp_full_size_data,
+ ],
+ 'stats' => [
+ 'original_size' => $wp_full_size_data['original_size'],
+ 'optimized_size' => $wp_full_size_data['optimized_size'],
+ 'percent' => $wp_full_size_data['percent'],
+ ],
+ ],
+ ] );
+
+ $ngg_process = imagify_get_optimization_process( $image->pid, 'ngg' );
+
+ if ( ! $ngg_process->is_valid() ) {
+ // WTF.
+ return $image;
+ }
+
+ // Copy the backup file (we don't want to backup the optimized file if it can be avoided).
+ $ngg_media = $ngg_process->get_media();
+ $wp_media = $wp_process->get_media();
+ $wp_backup_path = $wp_media->get_backup_path();
+ $filesystem = imagify_get_filesystem();
+ $backup_copied = false;
+
+ if ( $wp_backup_path ) {
+ $ngg_backup_path = $ngg_media->get_raw_backup_path();
+ $backup_copied = $filesystem->copy( $wp_backup_path, $ngg_backup_path, true );
+
+ if ( $backup_copied ) {
+ $filesystem->chmod_file( $ngg_backup_path );
+ }
+ }
+
+ /**
+ * Webp for the full size.
+ * Look for an existing copy locally:
+ * - if it exists, copy it (and its optimization data),
+ * - if not, add it to the optimization queue.
+ */
+ $add_full_webp = $wp_media->is_image() && get_imagify_option( 'convert_to_webp' );
+
+ if ( $add_full_webp ) {
+ // It's a supported image and webp conversion is enabled.
+ $wp_full_path_webp = false;
+ $webp_size_name = 'full' . $wp_process::WEBP_SUFFIX;
+ $wp_webp_data = $wp_data->get_size_data( $webp_size_name );
+
+ // Get the path to the webp image if it exists.
+ $wp_full_path_webp = $wp_process->get_fullsize_file()->get_path_to_webp();
+
+ if ( $wp_full_path_webp && ! $filesystem->exists( $wp_full_path_webp ) ) {
+ $wp_full_path_webp = false;
+ }
+
+ if ( $wp_full_path_webp ) {
+ // We know we have a webp version. Make sure we have the right data.
+ $wp_webp_data['success'] = true;
+
+ if ( empty( $wp_webp_data['original_size'] ) ) {
+ // The webp data is missing.
+ $full_size_weight = $wp_full_size_data['original_size'];
+
+ if ( ! $full_size_weight && $wp_backup_path ) {
+ // For some reason we don't have the original file weight, but we can get it from the backup file.
+ $full_size_weight = $filesystem->size( $wp_backup_path );
+
+ if ( $full_size_weight ) {
+ $wp_webp_data['original_size'] = $full_size_weight;
+ }
+ }
+ }
+
+ if ( ! empty( $wp_webp_data['original_size'] ) && empty( $wp_webp_data['optimized_size'] ) ) {
+ // The webp file size.
+ $wp_webp_data['optimized_size'] = $filesystem->size( $wp_full_path_webp );
+ }
+
+ if ( empty( $wp_webp_data['original_size'] ) || empty( $wp_webp_data['optimized_size'] ) ) {
+ // We must have both original and optimized sizes.
+ $wp_webp_data = [];
+ }
+ }
+
+ if ( $wp_full_path_webp && $wp_webp_data ) {
+ // We have the file and the data.
+ // Copy the file.
+ $ngg_full_file = new File( $ngg_media->get_raw_fullsize_path() );
+ $ngg_full_path_webp = $ngg_full_file->get_path_to_webp(); // Destination.
+
+ if ( $ngg_full_path_webp ) {
+ $copied = $filesystem->copy( $wp_full_path_webp, $ngg_full_path_webp, true );
+
+ if ( $copied ) {
+ // Success.
+ $filesystem->chmod_file( $ngg_full_path_webp );
+ $add_full_webp = false;
+ }
+ }
+
+ if ( ! $add_full_webp ) {
+ // The webp file has been successfully copied: now, copy the data.
+ $ngg_process->get_data()->update_size_optimization_data( $webp_size_name, $wp_webp_data );
+ }
+ }
+ }
+
+ // Optimize thumbnails.
+ $sizes = $ngg_media->get_media_files();
+ unset( $sizes['full'] );
+
+ if ( $add_full_webp ) {
+ // We could not use a local webp copy: ask for a new one.
+ $sizes[ $webp_size_name ] = [];
+ }
+
+ if ( ! $sizes ) {
+ return $image;
+ }
+
+ $args = [
+ 'hook_suffix' => 'optimize_imported_images',
+ ];
+
+ $ngg_process->optimize_sizes( array_keys( $sizes ), $optimization_level, $args );
+
+ return $image;
+}
+
+add_action( 'ngg_generated_image', 'imagify_ngg_maybe_add_dynamic_thumbnail_to_background_process', IMAGIFY_INT_MAX, 2 );
+/**
+ * Add a dynamically generated thumbnail to the background process queue.
+ * Note that this wonât work when images are imported (from WP Library or uploaded), since they are already being processed, and locked.
+ *
+ * @since 1.8
+ * @since 1.9 Doesn't use the class Imagify_NGG_Dynamic_Thumbnails_Background_Process anymore.
+ * @author Grégory Viguier
+ *
+ * @param object $image A NGG image object.
+ * @param string $size The thumbnail size name.
+ */
+function imagify_ngg_maybe_add_dynamic_thumbnail_to_background_process( $image, $size ) {
+ NGG\DynamicThumbnails::get_instance()->push_to_queue( $image, $size );
+}
+
+add_action( 'ngg_delete_picture', 'imagify_ngg_cleanup_after_media_deletion', 10, 2 );
+/**
+ * Delete everything when a NGG image is deleted.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param int $image_id The image ID.
+ * @param object $image A NGG object.
+ */
+function imagify_ngg_cleanup_after_media_deletion( $image_id, $image ) {
+ $process = imagify_get_optimization_process( $image, 'ngg' );
+
+ if ( ! $process->is_valid() ) {
+ return;
+ }
+
+ // Trigger a common hook.
+ imagify_trigger_delete_media_hook( $process );
+
+ /**
+ * The backup file has already been deleted by NGG.
+ * Delete the webp versions and the optimization data.
+ */
+ $process->delete_webp_files();
+ $process->get_data()->delete_optimization_data();
+}
+
+add_filter( 'imagify_crop_thumbnail', 'imagify_ngg_should_crop_thumbnail', 10, 4 );
+/**
+ * In case of a dynamic thumbnail, tell if the image must be croped or resized.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $crop True to crop the thumbnail, false to resize. Null by default.
+ * @param string $size Name of the thumbnail size.
+ * @param array $size_data Data of the thumbnail being processed. Contains at least 'width', 'height', and 'path'.
+ * @param MediaInterface $media The MediaInterface instance corresponding to the image being processed.
+ * @return bool
+ */
+function imagify_ngg_should_crop_thumbnail( $crop, $size, $size_data, $media ) {
+ static $data_per_media = [];
+ static $storage_per_media = [];
+
+ if ( 'ngg' !== $media->get_context() ) {
+ return $crop;
+ }
+
+ $media_id = $media->get_id();
+
+ if ( ! isset( $data_per_media[ $media_id ] ) ) {
+ $image = \nggdb::find_image( $media_id );
+
+ if ( ! empty( $image->_ngiw ) ) {
+ $storage_per_media[ $media_id ] = $image->_ngiw->get_storage()->object;
+ } else {
+ $storage_per_media[ $media_id ] = \C_Gallery_Storage::get_instance()->object;
+ }
+
+ $data_per_media[ $media_id ] = $storage_per_media[ $media_id ]->_image_mapper->find( $media_id ); // stdClass Object.
+ }
+
+ $params = $storage_per_media[ $media_id ]->get_image_size_params( $data_per_media[ $media_id ], $size );
+
+ return ! empty( $params['crop'] );
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/functions/admin-stats.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/functions/admin-stats.php
new file mode 100644
index 00000000..6805f11d
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/functions/admin-stats.php
@@ -0,0 +1,198 @@
+prefix . 'ngg_pictures';
+ $count = (int) $wpdb->get_var( "SELECT COUNT($table_name.pid) FROM $table_name" ); // WPCS: unprepared SQL ok.
+
+ return $count;
+}
+
+/**
+ * Count number of optimized attachments with an error.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @return int The number of attachments.
+ */
+function imagify_ngg_count_error_attachments() {
+ static $count;
+
+ if ( isset( $count ) ) {
+ return $count;
+ }
+
+ $ngg_db = DB::get_instance();
+ $key = $ngg_db->get_primary_key();
+ $count = (int) $ngg_db->get_var_by( "COUNT($key)", 'status', 'error' );
+
+ return $count;
+}
+
+/**
+ * Count number of optimized attachments (by Imagify or an other tool before).
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @return int The number of attachments.
+ */
+function imagify_ngg_count_optimized_attachments() {
+ static $count;
+
+ if ( isset( $count ) ) {
+ return $count;
+ }
+
+ $ngg_db = DB::get_instance();
+ $key = $ngg_db->get_primary_key();
+ $count = (int) $ngg_db->get_var_in( "COUNT($key)", 'status', array( 'success', 'already_optimized' ) );
+
+ return $count;
+}
+
+/**
+ * Count number of unoptimized attachments.
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ *
+ * @return int The number of attachments.
+ */
+function imagify_ngg_count_unoptimized_attachments() {
+ return imagify_ngg_count_attachments() - imagify_ngg_count_optimized_attachments() - imagify_ngg_count_error_attachments();
+}
+
+/**
+ * Count percent of optimized attachments.
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ *
+ * @return int The percent of optimized attachments.
+ */
+function imagify_ngg_percent_optimized_attachments() {
+ $total_attachments = imagify_ngg_count_attachments();
+ $total_optimized_attachments = imagify_ngg_count_optimized_attachments();
+
+ if ( ! $total_attachments || ! $total_optimized_attachments ) {
+ return 0;
+ }
+
+ return min( round( 100 * $total_optimized_attachments / $total_attachments ), 100 );
+}
+
+/**
+ * Count percent, original & optimized size of all images optimized by Imagify.
+ *
+ * @since 1.5
+ * @since 1.6.7 Revamped to handle huge libraries.
+ * @author Jonathan Buttigieg
+ *
+ * @param bool|array $attachments An array containing the keys 'count', 'original_size', and 'optimized_size', or an array of attachments (back compat', deprecated), or false.
+ * @return array An array containing the keys 'count', 'original_size', and 'optimized_size'.
+ */
+function imagify_ngg_count_saving_data( $attachments ) {
+ global $wpdb;
+
+ if ( is_array( $attachments ) ) {
+ return $attachments;
+ }
+
+ /**
+ * Filter the query to get all optimized NGG attachments.
+ * 3rd party will be able to override the result.
+ *
+ * @since 1.6.7
+ *
+ * @param bool|array $attachments An array containing the keys ('count', 'original_size', and 'optimized_size'), or false.
+ */
+ $attachments = apply_filters( 'imagify_ngg_count_saving_data', false );
+
+ if ( is_array( $attachments ) ) {
+ return $attachments;
+ }
+
+ $original_size = 0;
+ $optimized_size = 0;
+ $count = 0;
+
+ /** This filter is documented in /inc/functions/admin-stats.php */
+ $limit = apply_filters( 'imagify_count_saving_data_limit', 15000 );
+ $limit = absint( $limit );
+ $offset = 0;
+ $query = "
+ SELECT data
+ FROM $wpdb->ngg_imagify_data
+ WHERE status = 'success'
+ LIMIT %d, %d";
+
+ $attachments = $wpdb->get_col( $wpdb->prepare( $query, $offset, $limit ) ); // WPCS: unprepared SQL ok.
+ $wpdb->flush();
+
+ while ( $attachments ) {
+ $attachments = array_map( 'maybe_unserialize', $attachments );
+
+ foreach ( $attachments as $attachment_data ) {
+ if ( ! $attachment_data ) {
+ continue;
+ }
+
+ ++$count;
+ $original_data = $attachment_data['sizes']['full'];
+
+ // Increment the original sizes.
+ $original_size += $original_data['original_size'] ? $original_data['original_size'] : 0;
+ $optimized_size += $original_data['optimized_size'] ? $original_data['optimized_size'] : 0;
+
+ unset( $attachment_data['sizes']['full'], $original_data );
+
+ // Increment the thumbnails sizes.
+ foreach ( $attachment_data['sizes'] as $size_data ) {
+ if ( ! empty( $size_data['success'] ) ) {
+ $original_size += $size_data['original_size'] ? $size_data['original_size'] : 0;
+ $optimized_size += $size_data['optimized_size'] ? $size_data['optimized_size'] : 0;
+ }
+ }
+
+ unset( $size_data );
+ }
+
+ unset( $attachment_data );
+
+ if ( count( $attachments ) === $limit ) {
+ // Unless we are really unlucky, we still have attachments to fetch.
+ $offset += $limit;
+
+ $attachments = $wpdb->get_col( $wpdb->prepare( $query, $offset, $limit ) ); // WPCS: unprepared SQL ok.
+ $wpdb->flush();
+ } else {
+ // Save one request, don't go back to the beginning of the loop.
+ $attachments = array();
+ }
+ } // End while().
+
+ return array(
+ 'count' => $count,
+ 'original_size' => $original_size,
+ 'optimized_size' => $optimized_size,
+ );
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/functions/attachments.php b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/functions/attachments.php
new file mode 100644
index 00000000..b4cb4d81
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/nextgen-gallery/inc/functions/attachments.php
@@ -0,0 +1,21 @@
+init();
+NGG\DB::get_instance()->init();
+
+if ( is_admin() ) {
+ require IMAGIFY_PATH . 'inc/3rd-party/nextgen-gallery/inc/admin/enqueue.php';
+ require IMAGIFY_PATH . 'inc/3rd-party/nextgen-gallery/inc/admin/menu.php';
+ require IMAGIFY_PATH . 'inc/3rd-party/nextgen-gallery/inc/admin/gallery.php';
+ require IMAGIFY_PATH . 'inc/3rd-party/nextgen-gallery/inc/admin/bulk.php';
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/regenerate-thumbnails/classes/Main.php b/wp-content/plugins/imagify/inc/3rd-party/regenerate-thumbnails/classes/Main.php
new file mode 100644
index 00000000..d39d9bd1
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/regenerate-thumbnails/classes/Main.php
@@ -0,0 +1,401 @@
+get_param( 'id' );
+
+ if ( ! $this->set_process( $media_id ) ) {
+ return $dispatch_result;
+ }
+
+ $media_id = $this->get_process( $media_id )->get_media()->get_id();
+
+ // The attachment can be regenerated: keep the optimized full-sized file safe, and replace it by the backup file.
+ $this->backup_optimized_file( $media_id );
+ // Prevent automatic optimization.
+ \Imagify_Auto_Optimization::prevent_optimization( $media_id );
+ // Launch the needed hook.
+ add_filter( 'wp_generate_attachment_metadata', [ $this, 'launch_async_optimization' ], IMAGIFY_INT_MAX - 30, 2 );
+
+ return $dispatch_result;
+ }
+
+ /**
+ * Auto-optimize after an attachment is regenerated.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $metadata An array of attachment meta data, containing the sizes that have been regenerated.
+ * @param int $media_id Current media ID.
+ * @return array
+ */
+ public function launch_async_optimization( $metadata, $media_id ) {
+ $process = $this->get_process( $media_id );
+
+ if ( ! $process ) {
+ return $metadata;
+ }
+
+ $sizes = isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : [];
+ $media = $process->get_media();
+ $fullsize_path = $media->get_raw_fullsize_path();
+
+ if ( $fullsize_path ) {
+ $original_path = $media->get_original_path();
+
+ if ( $original_path && $this->is_wp_53() && $original_path !== $fullsize_path ) {
+ /**
+ * The original file and the full-sized file are not the same:
+ * That means wp_generate_attachment_metadata() recreated the full-sized file, based on the original one.
+ * So it must be optimized again now.
+ */
+ $sizes['full'] = [];
+ }
+ }
+
+ if ( ! $sizes ) {
+ // Put the optimized full-sized file back.
+ $this->put_optimized_file_back( $media_id );
+ return $metadata;
+ }
+
+ /**
+ * Optimize the sizes that have been regenerated.
+ */
+ // If the media has webp versions, recreate them for the sizes that have been regenerated.
+ $data = $process->get_data();
+ $optimization_data = $data->get_optimization_data();
+
+ if ( ! empty( $optimization_data['sizes'] ) ) {
+ foreach ( $optimization_data['sizes'] as $size_name => $size_data ) {
+ $non_webp_size_name = $process->is_size_webp( $size_name );
+
+ if ( ! $non_webp_size_name || ! isset( $sizes[ $non_webp_size_name ] ) ) {
+ continue;
+ }
+
+ // Add the webp size.
+ $sizes[ $size_name ] = [];
+ }
+ }
+
+ $sizes = array_keys( $sizes );
+ $optimization_level = $data->get_optimization_level();
+ $optimization_args = [ 'hook_suffix' => static::HOOK_SUFFIX ];
+
+ // Delete related optimization data or nothing will be optimized.
+ $data->delete_sizes_optimization_data( $sizes );
+ $process->optimize_sizes( $sizes, $optimization_level, $optimization_args );
+
+ $this->unset_process( $media_id );
+
+ return $metadata;
+ }
+
+ /**
+ * Fires after regenerating the thumbnails.
+ * This puts the full-sized optimized file back.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process.
+ * @param array $item The item being processed. See $this->task().
+ */
+ public function after_regenerate_thumbnails( $process, $item ) {
+ $media_id = $process->get_media()->get_id();
+
+ $this->processes[ $media_id ] = $process;
+
+ $this->put_optimized_file_back( $media_id );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** INTERNAL TOOLS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Set an optimization process.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ * @access protected
+ *
+ * @param int $media_id The media ID.
+ * @return bool
+ */
+ protected function set_process( $media_id ) {
+ if ( ! $media_id || ! Imagify_Requirements::is_api_key_valid() ) {
+ return false;
+ }
+
+ $process = imagify_get_optimization_process( $media_id, 'wp' );
+
+ if ( ! $process->is_valid() || ! $process->get_media()->is_image() || ! $process->get_data()->is_optimized() ) {
+ // Invalid, not animage, or no optimization have been attempted yet.
+ return false;
+ }
+
+ $this->processes[ $media_id ] = $process;
+
+ return true;
+ }
+
+ /**
+ * Unset an optimization process.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ */
+ protected function unset_process( $media_id ) {
+ unset( $this->processes[ $media_id ] );
+ }
+
+ /**
+ * Unset an optimization process.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @return ProcessInterface|bool An optimization process object. False on failure.
+ */
+ protected function get_process( $media_id ) {
+ return ! empty( $this->processes[ $media_id ] ) ? $this->processes[ $media_id ] : false;
+ }
+
+ /**
+ * Backup the optimized full-sized file and replace it by the original backup file.
+ *
+ * @since 1.7.1
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id Media ID.
+ */
+ protected function backup_optimized_file( $media_id ) {
+ $media = $this->get_process( $media_id )->get_media();
+ $fullsize_path = $media->get_raw_fullsize_path();
+
+ if ( ! $fullsize_path ) {
+ // Uh?
+ return;
+ }
+
+ $original_path = $media->get_original_path();
+
+ if ( $original_path && $this->is_wp_53() && $original_path !== $fullsize_path ) {
+ /**
+ * The original file and the full-sized file are not the same:
+ * That means wp_generate_attachment_metadata() will recreate the full-sized file, based on the original one.
+ * Then, the thumbnails will be created from a newly created (unoptimized) file.
+ */
+ return;
+ }
+
+ $backup_path = $media->get_backup_path();
+
+ if ( ! $backup_path ) {
+ // No backup file, too bad.
+ return;
+ }
+
+ /**
+ * Replace the optimized full-sized file by the backup, so any optimization will not use an optimized file, but the original one.
+ * The optimized full-sized file is kept and renamed, and will be put back in place at the end of the optimization process.
+ */
+ $filesystem = \Imagify_Filesystem::get_instance();
+
+ if ( $filesystem->exists( $fullsize_path ) ) {
+ $tmp_file_path = static::get_temporary_file_path( $fullsize_path );
+ $moved = $filesystem->move( $fullsize_path, $tmp_file_path, true );
+ }
+
+ $filesystem->copy( $backup_path, $fullsize_path );
+ }
+
+ /**
+ * Put the optimized full-sized file back.
+ *
+ * @since 1.7.1
+ * @since 1.9 Replaced $attachment parameter by $media_id.
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id Media ID.
+ */
+ protected function put_optimized_file_back( $media_id ) {
+ $file_path = $this->get_process( $media_id )->get_media()->get_raw_fullsize_path();
+ $tmp_file_path = static::get_temporary_file_path( $file_path );
+ $filesystem = \Imagify_Filesystem::get_instance();
+
+ if ( $filesystem->exists( $tmp_file_path ) ) {
+ $moved = $filesystem->move( $tmp_file_path, $file_path, true );
+ }
+ }
+
+ /**
+ * Tell if weâre playing in WP 5.3âs garden.
+ *
+ * @since 1.9.8
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ protected function is_wp_53() {
+ if ( isset( $this->is_wp53 ) ) {
+ return $this->is_wp53;
+ }
+
+ $this->is_wp53 = function_exists( 'wp_get_original_image_path' );
+
+ return $this->is_wp53;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PUBLIC TOOLS ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the beginning of the route used to regenerate thumbnails.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public static function get_route_prefix() {
+ static $route;
+
+ if ( ! isset( $route ) ) {
+ $regen = \RegenerateThumbnails();
+
+ if ( ( empty( $regen->rest_api ) || ! is_object( $regen->rest_api ) ) && method_exists( $regen, 'rest_api_init' ) ) {
+ $regen->rest_api_init();
+ }
+
+ $route = '/' . trim( $regen->rest_api->namespace, '/' ) . '/regenerate/';
+ }
+
+ return $route;
+ }
+
+ /**
+ * Get the path to the temporary file.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The optimized full-sized file path.
+ * @return string
+ */
+ public static function get_temporary_file_path( $file_path ) {
+ return $file_path . '_backup';
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/regenerate-thumbnails/regenerate-thumbnails.php b/wp-content/plugins/imagify/inc/3rd-party/regenerate-thumbnails/regenerate-thumbnails.php
new file mode 100644
index 00000000..e307559f
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/regenerate-thumbnails/regenerate-thumbnails.php
@@ -0,0 +1,10 @@
+has_notices() && ( $notices->display_welcome_steps() || $notices->display_wrong_api_key() ) ) {
+ // We display a notice that uses SweetAlert.
+ imagify_wprml_dequeue();
+ return;
+ }
+
+ if ( imagify_is_screen( 'bulk' ) || imagify_is_screen( 'imagify-settings' ) ) {
+ // We display a page that uses SweetAlert.
+ imagify_wprml_dequeue();
+ return;
+ }
+
+ if ( function_exists( 'imagify_get_ngg_bulk_screen_id' ) && imagify_is_screen( imagify_get_ngg_bulk_screen_id() ) ) {
+ // We display the NGG Bulk Optimization page.
+ imagify_wprml_dequeue();
+ }
+ }
+
+ /**
+ * Prevent WP Real Media Library to enqueue its styles and scripts.
+ *
+ * @since 1.6.13
+ * @author Grégory Viguier
+ */
+ function imagify_wprml_dequeue() {
+ $instance = \MatthiasWeb\RealMediaLibrary\general\Backend::getInstance();
+
+ remove_action( 'admin_enqueue_scripts', [ $instance, 'admin_enqueue_scripts' ], 0 );
+ remove_action( 'admin_footer', [ $instance, 'admin_footer' ] );
+
+ if ( class_exists( '\\MatthiasWeb\\RealMediaLibrary\\general\\FolderShortcode' ) ) {
+ $instance = \MatthiasWeb\RealMediaLibrary\general\FolderShortcode::getInstance();
+
+ remove_action( 'admin_head', [ $instance, 'admin_head' ] );
+ remove_action( 'admin_enqueue_scripts', [ $instance, 'admin_enqueue_scripts' ] );
+ }
+
+ if ( class_exists( '\\MatthiasWeb\\RealMediaLibrary\\comp\\PageBuilders' ) ) {
+ $instance = \MatthiasWeb\RealMediaLibrary\comp\PageBuilders::getInstance();
+
+ remove_action( 'init', [ $instance, 'init' ] );
+ }
+ }
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/3rd-party/wp-rocket/classes/Main.php b/wp-content/plugins/imagify/inc/3rd-party/wp-rocket/classes/Main.php
new file mode 100644
index 00000000..06093979
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/wp-rocket/classes/Main.php
@@ -0,0 +1,116 @@
+= 0 ) {
+ return;
+ }
+
+ if ( ! imagify_is_screen( 'settings_page_' . WP_ROCKET_PLUGIN_SLUG ) && ! imagify_is_screen( 'settings_page_' . WP_ROCKET_PLUGIN_SLUG . '-network' ) ) {
+ return;
+ }
+
+ remove_action( 'all_admin_notices', [ \Imagify_Notices::get_instance(), 'render_notices' ] );
+ remove_action( 'admin_enqueue_scripts', [ \Imagify_Assets::get_instance(), 'enqueue_styles_and_scripts' ], IMAGIFY_INT_MAX );
+ }
+
+ /**
+ * Provide a custom CDN source.
+ *
+ * @since 1.9.3
+ * @author Grégory Viguier
+ *
+ * @param array $source {
+ * An array of arguments.
+ *
+ * @type $name string The name of which provides the URL (plugin name, etc).
+ * @type $url string The CDN URL.
+ * }
+ * @return array
+ */
+ public function set_cdn_source( $source ) {
+ if ( ! function_exists( 'get_rocket_option' ) ) {
+ return $source;
+ }
+
+ if ( ! get_rocket_option( 'cdn' ) ) {
+ return $source;
+ }
+
+ $container = apply_filters( 'rocket_container', null );
+
+ if ( is_object( $container ) && method_exists( $container, 'get' ) ) {
+ $cdn = $container->get( 'cdn' );
+
+ if ( $cdn && method_exists( $cdn, 'get_cdn_urls' ) ) {
+ $url = $cdn->get_cdn_urls( [ 'all', 'images' ] );
+ }
+ }
+
+ if ( ! isset( $url ) && function_exists( 'get_rocket_cdn_cnames' ) ) {
+ $url = get_rocket_cdn_cnames( [ 'all', 'images' ] );
+ }
+
+ if ( empty( $url ) ) {
+ return $source;
+ }
+
+ $url = reset( $url );
+
+ if ( ! $url ) {
+ return $source;
+ }
+
+ if ( ! preg_match( '@^(https?:)?//@i', $url ) ) {
+ $url = '//' . $url;
+ }
+
+ $scheme = wp_parse_url( \Imagify_Filesystem::get_instance()->get_site_root_url() );
+ $scheme = ! empty( $scheme['scheme'] ) ? $scheme['scheme'] : null;
+ $url = set_url_scheme( $url, $scheme );
+
+ $source['name'] = 'WP Rocket';
+ $source['url'] = $url;
+
+ return $source;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/3rd-party/wp-rocket/wp-rocket.php b/wp-content/plugins/imagify/inc/3rd-party/wp-rocket/wp-rocket.php
new file mode 100644
index 00000000..ced2a28c
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/wp-rocket/wp-rocket.php
@@ -0,0 +1,8 @@
+init();
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/3rd-party/yoast-seo.php b/wp-content/plugins/imagify/inc/3rd-party/yoast-seo.php
new file mode 100644
index 00000000..a5edfb79
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/3rd-party/yoast-seo.php
@@ -0,0 +1,21 @@
+base && 'attachment' === $current_screen->post_type ) {
+ wp_dequeue_script( 'yoast-seo' );
+ wp_deregister_script( 'yoast-seo' );
+ }
+ }
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/admin/custom-folders.php b/wp-content/plugins/imagify/inc/admin/custom-folders.php
new file mode 100644
index 00000000..0111b109
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/admin/custom-folders.php
@@ -0,0 +1,85 @@
+can_operate() || ! $files_db->can_operate() ) {
+ return;
+ }
+
+ foreach ( $folders_to_sync as $folder_path ) {
+ $folder_path = trailingslashit( $folder_path );
+
+ if ( Imagify_Files_Scan::is_path_forbidden( $folder_path ) ) {
+ // This theme or plugin must not be optimized.
+ continue;
+ }
+
+ // Get the related folder.
+ $placeholder = Imagify_Files_Scan::add_placeholder( $folder_path );
+ $folder = Imagify_Folders_DB::get_instance()->get_in( 'path', $placeholder );
+
+ if ( ! $folder ) {
+ // This theme or plugin is not in the database.
+ continue;
+ }
+
+ // Sync the folder files.
+ Imagify_Custom_Folders::synchronize_files_from_folders( array(
+ $folder['folder_id'] => array(
+ 'folder_id' => $folder['folder_id'],
+ 'path' => $placeholder,
+ 'active' => $folder['active'],
+ 'folder_path' => $folder_path,
+ ),
+ ) );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/admin/media.php b/wp-content/plugins/imagify/inc/admin/media.php
new file mode 100644
index 00000000..31f52d05
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/admin/media.php
@@ -0,0 +1,98 @@
+current_user_can( 'manual-optimize', $post->ID ) ) {
+ return $form_fields;
+ }
+
+ $process = imagify_get_optimization_process( $post->ID, 'wp' );
+
+ $form_fields['imagify'] = array(
+ 'label' => 'Imagify',
+ 'input' => 'html',
+ 'html' => get_imagify_media_column_content( $process ),
+ 'show_in_edit' => true,
+ 'show_in_modal' => true,
+ );
+
+ return $form_fields;
+}
+
+add_filter( 'media_row_actions', '_imagify_add_actions_to_media_list_row', IMAGIFY_INT_MAX, 2 );
+/**
+ * Add "Compare Original VS Optimized" link to the media row action
+ *
+ * @since 1.4.3
+ * @author Geoffrey Crofte
+ * @param array $actions An array of action links for each attachment.
+ * Default 'Edit', 'Delete Permanently', 'View'.
+ * @param object $post WP_Post object for the current attachment.
+ * @return array
+ */
+function _imagify_add_actions_to_media_list_row( $actions, $post ) {
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manual-optimize', $post->ID ) ) {
+ return $actions;
+ }
+
+ $process = imagify_get_optimization_process( $post->ID, 'wp' );
+
+ if ( ! $process->is_valid() ) {
+ return $actions;
+ }
+
+ $media = $process->get_media();
+
+ // If this media is not an image, do nothing.
+ if ( ! $media->is_supported() || ! $media->is_image() ) {
+ return $actions;
+ }
+
+ $data = $process->get_data();
+
+ // If Imagify license not valid, or image is not optimized, do nothing.
+ if ( ! Imagify_Requirements::is_api_key_valid() || ! $data->is_optimized() ) {
+ return $actions;
+ }
+
+ // If no backup, do nothing.
+ if ( ! $media->has_backup() ) {
+ return $actions;
+ }
+
+ $dimensions = $media->get_dimensions();
+
+ // If full image is too small. See get_imagify_localize_script_translations().
+ if ( $dimensions['width'] < 360 ) {
+ return $actions;
+ }
+
+ // Else, add action link for comparison (JS triggered).
+ $actions['imagify-compare'] = Imagify_Views::get_instance()->get_template( 'button/compare-images', [
+ 'url' => get_edit_post_link( $media->get_id() ) . '#imagify-compare',
+ 'backup_url' => $media->get_backup_url(),
+ 'original_url' => $media->get_fullsize_url(),
+ 'media_id' => $media->get_id(),
+ 'width' => $dimensions['width'],
+ 'height' => $dimensions['height'],
+ ] );
+
+ return $actions;
+}
diff --git a/wp-content/plugins/imagify/inc/admin/meta-boxes.php b/wp-content/plugins/imagify/inc/admin/meta-boxes.php
new file mode 100644
index 00000000..e87475c3
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/admin/meta-boxes.php
@@ -0,0 +1,89 @@
+current_user_can( 'manual-optimize', $post->ID ) ) {
+ return;
+ }
+
+ $process = imagify_get_optimization_process( $post->ID, 'wp' );
+
+ if ( ! $process->is_valid() ) {
+ return;
+ }
+
+ $media = $process->get_media();
+
+ if ( ! $media->is_supported() ) {
+ return;
+ }
+
+ if ( ! $media->has_required_media_data() ) {
+ return;
+ }
+
+ $data = $process->get_data();
+ $views = Imagify_Views::get_instance();
+
+ if ( ! Imagify_Requirements::is_api_key_valid() && ! $data->is_optimized() ) {
+ ?>
+
+
+ is_locked();
+
+ if ( $is_locked ) {
+ switch ( $is_locked ) {
+ case 'optimizing':
+ $lock_label = __( 'Optimizing...', 'imagify' );
+ break;
+ case 'restoring':
+ $lock_label = __( 'Restoring...', 'imagify' );
+ break;
+ default:
+ $lock_label = __( 'Processing...', 'imagify' );
+ }
+ ?>
+
+ print_template( 'button/processing', [ 'label' => $lock_label ] ); ?>
+
+ is_optimized() || $data->is_already_optimized() || $data->is_error() ) {
+ ?>
+
+
+
+
+ $post->ID ) );
+ ?>
+
+ has_backup() && $data->is_optimized() ) {
+ ?>
+
+
+ get( 'version' );
+ // Version stored at the site level.
+ $site_version = Imagify_Data::get_instance()->get( 'version' );
+
+ if ( ! $network_version ) {
+ // First install (network).
+
+ /**
+ * Triggered on Imagify first install (network).
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ */
+ do_action( 'imagify_first_network_install' );
+ } elseif ( IMAGIFY_VERSION !== $network_version ) {
+ // Already installed but got updated (network).
+
+ /**
+ * Triggered on Imagify upgrade (network).
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param string $network_version Previous version stored on the network.
+ * @param string $site_version Previous version stored on site level.
+ */
+ do_action( 'imagify_network_upgrade', $network_version, $site_version );
+ }
+
+ // If any upgrade has been done, we flush and update version.
+ if ( did_action( 'imagify_first_network_install' ) || did_action( 'imagify_network_upgrade' ) ) {
+ Imagify_Options::get_instance()->set( 'version', IMAGIFY_VERSION );
+ }
+
+ if ( ! $site_version ) {
+ // First install (site level).
+
+ /**
+ * Triggered on Imagify first install (site level).
+ *
+ * @since 1.0
+ */
+ do_action( 'imagify_first_install' );
+ } elseif ( IMAGIFY_VERSION !== $site_version ) {
+ // Already installed but got updated (site level).
+
+ /**
+ * Triggered on Imagify upgrade (site level).
+ *
+ * @since 1.0
+ * @since 1.7 $network_version replaces the "new version" (which can easily be grabbed with the constant).
+ *
+ * @param string $network_version Previous version stored on the network.
+ * @param string $site_version Previous version stored on site level.
+ */
+ do_action( 'imagify_upgrade', $network_version, $site_version );
+ }
+
+ // If any upgrade has been done, we flush and update version.
+ if ( did_action( 'imagify_first_install' ) || did_action( 'imagify_upgrade' ) ) {
+ Imagify_Data::get_instance()->set( 'version', IMAGIFY_VERSION );
+ }
+}
+
+/**
+ * Upgrade the upgrader:
+ * Imagify 1.7 splits "network version" and "site version". Since the "site version" didn't exist before 1.7, we need to provide a version based on the "network version".
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ */
+function imagify_upgrader_upgrade() {
+ global $wpdb;
+
+ // Version stored on the network.
+ $network_version = Imagify_Options::get_instance()->get( 'version' );
+
+ if ( ! $network_version ) {
+ // Really first install.
+ return;
+ }
+
+ // Version stored at the site level.
+ $site_version = Imagify_Data::get_instance()->get( 'version' );
+
+ if ( $site_version ) {
+ // This site's upgrader is already upgraded.
+ return;
+ }
+
+ if ( ! is_multisite() ) {
+ // Not a multisite, so both versions must have the same value.
+ Imagify_Data::get_instance()->set( 'version', $network_version );
+ return;
+ }
+
+ $sites = get_site_option( 'imagify_old_version' );
+
+ if ( IMAGIFY_VERSION !== $network_version && ! $sites ) {
+ // The network is not up-to-date yet: store the site IDs that must be updated.
+ $network_id = function_exists( 'get_current_network_id' ) ? get_current_network_id() : $wpdb->siteid;
+ $sites = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE site_id = %d AND archived = 0 AND deleted = 0", $network_id ) );
+ $sites = array_map( 'absint', $sites );
+ $sites = array_filter( $sites );
+
+ if ( ! $sites ) {
+ // Uh?
+ return;
+ }
+
+ // We store the old network version and the site Ids: those sites will need to be upgraded from this version.
+ $sites['version'] = $network_version;
+
+ add_site_option( 'imagify_old_version', $sites );
+ }
+
+ if ( empty( $sites['version'] ) ) {
+ // WTF.
+ delete_site_option( 'imagify_old_version' );
+ return;
+ }
+
+ $network_version = $sites['version'];
+ unset( $sites['version'] );
+
+ $sites = array_flip( $sites );
+ $site_id = get_current_blog_id();
+
+ if ( ! isset( $sites[ $site_id ] ) ) {
+ // This site is already upgraded.
+ return;
+ }
+
+ unset( $sites[ $site_id ] );
+
+ if ( ! $sites ) {
+ // We're done, all the sites have been upgraded.
+ delete_site_option( 'imagify_old_version' );
+ } else {
+ // Some sites still need to be upgraded.
+ $sites = array_flip( $sites );
+ $sites['version'] = $network_version;
+ update_site_option( 'imagify_old_version', $sites );
+ }
+
+ Imagify_Data::get_instance()->set( 'version', $network_version );
+}
+
+add_action( 'imagify_first_network_install', '_imagify_first_install' );
+/**
+ * Keeps this function up to date at each version.
+ *
+ * @since 1.0
+ */
+function _imagify_first_install() {
+ // Set a transient to know when we will have to display a notice to ask the user to rate the plugin.
+ set_site_transient( 'imagify_seen_rating_notice', true, DAY_IN_SECONDS * 3 );
+}
+
+add_action( 'imagify_upgrade', '_imagify_new_upgrade', 10, 2 );
+/**
+ * What to do when Imagify is updated, depending on versions.
+ *
+ * @since 1.0
+ * @since 1.7 $network_version replaces the "new version" (which can easily be grabbed with the constant).
+ *
+ * @param string $network_version Previous version stored on the network.
+ * @param string $site_version Previous version stored on site level.
+ */
+function _imagify_new_upgrade( $network_version, $site_version ) {
+ global $wpdb;
+
+ $options = Imagify_Options::get_instance();
+
+ // 1.2
+ if ( version_compare( $site_version, '1.2' ) < 0 ) {
+ // Update all already optimized images status from 'error' to 'already_optimized'.
+ $query = new WP_Query( array(
+ 'is_imagify' => true,
+ 'post_type' => 'attachment',
+ 'post_status' => imagify_get_post_statuses(),
+ 'post_mime_type' => imagify_get_mime_types(),
+ 'meta_key' => '_imagify_status',
+ 'meta_value' => 'error',
+ 'posts_per_page' => -1,
+ 'update_post_term_cache' => false,
+ 'no_found_rows' => true,
+ 'fields' => 'ids',
+ ) );
+
+ if ( $query->posts ) {
+ foreach ( (array) $query->posts as $id ) {
+ $data = get_post_meta( $id, '_imagify_data', true );
+ $error = ! empty( $data['sizes']['full']['error'] ) ? $data['sizes']['full']['error'] : '';
+
+ if ( false !== strpos( $error, 'This image is already compressed' ) ) {
+ update_post_meta( $id, '_imagify_status', 'already_optimized' );
+ }
+ }
+ }
+
+ // Auto-activate the Admin Bar option.
+ $options->set( 'admin_bar_menu', 1 );
+ }
+
+ // 1.3.2
+ if ( version_compare( $site_version, '1.3.2' ) < 0 ) {
+ // Update all already optimized images status from 'error' to 'already_optimized'.
+ $query = new WP_Query( array(
+ 'is_imagify' => true,
+ 'post_type' => 'attachment',
+ 'post_status' => imagify_get_post_statuses(),
+ 'post_mime_type' => imagify_get_mime_types(),
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ array(
+ 'key' => '_imagify_data',
+ 'compare' => 'EXISTS',
+ ),
+ array(
+ 'key' => '_imagify_optimization_level',
+ 'compare' => 'NOT EXISTS',
+ ),
+ ),
+ 'posts_per_page' => -1,
+ 'update_post_term_cache' => false,
+ 'no_found_rows' => true,
+ 'fields' => 'ids',
+ ) );
+
+ if ( $query->posts ) {
+ foreach ( (array) $query->posts as $id ) {
+ $data = get_post_meta( $id, '_imagify_data', true );
+ $stats = isset( $data['stats'] ) ? $data['stats'] : [];
+
+ if ( isset( $stats['aggressive'] ) ) {
+ update_post_meta( $id, '_imagify_optimization_level', (int) $stats['aggressive'] );
+ }
+ }
+ }
+ }
+
+ // 1.4.5
+ if ( version_compare( $site_version, '1.4.5' ) < 0 ) {
+ // Delete all transients used for async optimization.
+ $wpdb->query( $wpdb->prepare( "DELETE from $wpdb->options WHERE option_name LIKE %s", Imagify_DB::esc_like( '_transient_imagify-async-in-progress-' ) . '%' ) );
+ }
+
+ // 1.7
+ if ( version_compare( $site_version, '1.7' ) < 0 ) {
+ // Migrate data.
+ Imagify_Cron_Library_Size::get_instance()->do_event();
+
+ if ( ! imagify_is_active_for_network() ) {
+ // Make sure the settings are autoloaded.
+ $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->options} SET `autoload` = 'yes' WHERE `autoload` != 'yes' AND option_name = %s", $options->get_option_name() ) );
+ }
+
+ // Rename the option that stores the NGG table version. Since the table is also updated in 1.7, let's simply delete the option.
+ delete_option( $wpdb->prefix . 'ngg_imagify_data_db_version' );
+ }
+
+ // 1.8.1
+ if ( version_compare( $site_version, '1.8.1' ) < 0 ) {
+ // Custom folders: replace `{{ABSPATH}}/` by `{{ROOT}}/`.
+ $filesystem = imagify_get_filesystem();
+ $replacement = '{{ROOT}}/';
+
+ if ( $filesystem->has_wp_its_own_directory() ) {
+ $replacement .= preg_replace( '@^' . preg_quote( $filesystem->get_site_root(), '@' ) . '@', '', $filesystem->get_abspath() );
+ }
+
+ $replacement = Imagify_DB::esc_like( $replacement );
+
+ $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->base_prefix}imagify_files SET path = REPLACE( path, '{{ABSPATH}}/', %s ) WHERE path LIKE %s", $replacement, '{{ABSPATH}}/%' ) );
+ $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->base_prefix}imagify_folders SET path = REPLACE( path, '{{ABSPATH}}/', %s ) WHERE path LIKE %s", $replacement, '{{ABSPATH}}/%' ) );
+ }
+
+ // 1.8.2
+ if ( version_compare( $site_version, '1.8.2' ) < 0 ) {
+ Imagify_Options::get_instance()->set( 'partner_links', 1 );
+ }
+
+ // 1.9.6
+ if ( version_compare( $site_version, '1.9.6' ) < 0 ) {
+ \Imagify\Stats\OptimizedMediaWithoutWebp::get_instance()->clear_cache();
+ }
+
+ // 1.9.11
+ if ( version_compare( $site_version, '1.9.11' ) < 0 ) {
+ imagify_secure_custom_directories();
+ }
+}
+
+add_action( 'upgrader_process_complete', 'imagify_maybe_reset_opcache', 20, 2 );
+/**
+ * Maybe reset opcache after Imagify update.
+ *
+ * @since 1.7.1.2
+ * @author Grégory Viguier
+ *
+ * @param object $wp_upgrader Plugin_Upgrader instance.
+ * @param array $hook_extra {
+ * Array of bulk item update data.
+ *
+ * @type string $action Type of action. Default 'update'.
+ * @type string $type Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'.
+ * @type bool $bulk Whether the update process is a bulk update. Default true.
+ * @type array $plugins Array of the basename paths of the plugins' main files.
+ * }
+ */
+function imagify_maybe_reset_opcache( $wp_upgrader, $hook_extra ) {
+ static $imagify_path;
+
+ if ( ! isset( $hook_extra['action'], $hook_extra['type'], $hook_extra['plugins'] ) ) {
+ return;
+ }
+
+ if ( 'update' !== $hook_extra['action'] || 'plugin' !== $hook_extra['type'] || ! is_array( $hook_extra['plugins'] ) ) {
+ return;
+ }
+
+ $plugins = array_flip( $hook_extra['plugins'] );
+
+ if ( ! isset( $imagify_path ) ) {
+ $imagify_path = plugin_basename( IMAGIFY_FILE );
+ }
+
+ if ( ! isset( $plugins[ $imagify_path ] ) ) {
+ return;
+ }
+
+ imagify_reset_opcache();
+}
+
+/**
+ * Reset PHP opcache.
+ *
+ * @since 1.8.1
+ * @since 1.9.9 Added $reset_function_cache parameter and return boolean.
+ * @author Grégory Viguier
+ *
+ * @param bool $reset_function_cache Set to true to bypass the cache.
+ * @return bool Return true if the opcode cache was reset (or reset in a previous call), or false if the opcode cache is disabled.
+ */
+function imagify_reset_opcache( $reset_function_cache = false ) {
+ static $can_reset;
+
+ if ( $reset_function_cache || ! isset( $can_reset ) ) {
+ if ( ! function_exists( 'opcache_reset' ) ) {
+ $can_reset = false;
+ return false;
+ }
+
+ $opcache_enabled = filter_var( ini_get( 'opcache.enable' ), FILTER_VALIDATE_BOOLEAN ); // phpcs:ignore PHPCompatibility.IniDirectives.NewIniDirectives.opcache_enableFound
+
+ if ( ! $opcache_enabled ) {
+ $can_reset = false;
+ return false;
+ }
+
+ $restrict_api = ini_get( 'opcache.restrict_api' ); // phpcs:ignore PHPCompatibility.IniDirectives.NewIniDirectives.opcache_restrict_apiFound
+
+ if ( $restrict_api && strpos( __FILE__, $restrict_api ) !== 0 ) {
+ $can_reset = false;
+ return false;
+ }
+
+ $can_reset = true;
+ }
+
+ if ( ! $can_reset ) {
+ return false;
+ }
+
+ return opcache_reset(); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.opcache_resetFound
+}
+
+add_action( 'imagify_activation', 'imagify_secure_custom_directories' );
+/**
+ * Scan imagify directories and add `index.php` files where missing.
+ *
+ * @since 1.9.11
+ *
+ * @return void
+ */
+function imagify_secure_custom_directories() {
+ $filesystem = imagify_get_filesystem();
+
+ Imagify_Custom_Folders::add_indexes();
+
+ $conf_dir = $filesystem->get_site_root() . 'conf';
+ Imagify_Custom_Folders::add_indexes( $conf_dir );
+
+ $backup_dir = get_imagify_backup_dir_path();
+ Imagify_Custom_Folders::add_indexes( $backup_dir );
+}
diff --git a/wp-content/plugins/imagify/inc/admin/upload.php b/wp-content/plugins/imagify/inc/admin/upload.php
new file mode 100644
index 00000000..1a4927ec
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/admin/upload.php
@@ -0,0 +1,129 @@
+current_user_can( 'optimize' ) ) {
+ $columns['imagify_optimized_file'] = __( 'Imagify', 'imagify' );
+ }
+
+ return $columns;
+}
+
+add_action( 'manage_media_custom_column', '_imagify_manage_media_custom_column', 10, 2 );
+/**
+ * Add content to the "Imagify" columns in upload.php.
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ *
+ * @param string $column_name Name of the custom column.
+ * @param int $attachment_id Attachment ID.
+ */
+function _imagify_manage_media_custom_column( $column_name, $attachment_id ) {
+ if ( 'imagify_optimized_file' !== $column_name ) {
+ return;
+ }
+
+ $process = imagify_get_optimization_process( $attachment_id, 'wp' );
+
+ echo get_imagify_media_column_content( $process );
+}
+
+add_action( 'restrict_manage_posts', '_imagify_attachments_filter_dropdown' );
+/**
+ * Adds a dropdown that allows filtering on the attachments Imagify status.
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ */
+function _imagify_attachments_filter_dropdown() {
+ if ( ! Imagify_Views::get_instance()->is_wp_library_page() ) {
+ return;
+ }
+
+ $optimized = imagify_count_optimized_attachments();
+ $unoptimized = imagify_count_unoptimized_attachments();
+ $errors = imagify_count_error_attachments();
+ $status = isset( $_GET['imagify-status'] ) ? wp_unslash( $_GET['imagify-status'] ) : 0; // WPCS: CSRF ok.
+ $options = array(
+ 'optimized' => _x( 'Optimized', 'Media Files', 'imagify' ),
+ 'unoptimized' => _x( 'Unoptimized', 'Media Files', 'imagify' ),
+ 'errors' => _x( 'Errors', 'Media Files', 'imagify' ),
+ );
+
+ echo '' . __( 'Filter by status', 'imagify' ) . ' ';
+ echo '';
+ echo '' . __( 'All Media Files', 'imagify' ) . ' ';
+
+ foreach ( $options as $value => $label ) {
+ echo '' . $label . ' (' . ${$value} . ') ';
+ }
+ echo ' ';
+}
+
+add_filter( 'request', '_imagify_sort_attachments_by_status' );
+/**
+ * Modify the query based on the imagify-status variable in $_GET.
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ *
+ * @param array $vars The array of requested query variables.
+ * @return array
+ */
+function _imagify_sort_attachments_by_status( $vars ) {
+ if ( empty( $_GET['imagify-status'] ) || ! Imagify_Views::get_instance()->is_wp_library_page() ) { // WPCS: CSRF ok.
+ return $vars;
+ }
+
+ $status = wp_unslash( $_GET['imagify-status'] ); // WPCS: CSRF ok.
+ $meta_key = '_imagify_status';
+ $meta_compare = '=';
+ $relation = array();
+
+ switch ( $status ) {
+ case 'unoptimized':
+ $meta_key = '_imagify_data';
+ $meta_compare = 'NOT EXISTS';
+ break;
+ case 'optimized':
+ $status = 'success';
+ $relation = array(
+ 'key' => $meta_key,
+ 'value' => 'already_optimized',
+ 'compare' => $meta_compare,
+ );
+ break;
+ case 'errors':
+ $status = 'error';
+ break;
+ default:
+ return $vars;
+ }
+
+ $vars = array_merge( $vars, array(
+ 'meta_query' => array(
+ 'relation' => 'or',
+ array(
+ 'key' => $meta_key,
+ 'value' => $status,
+ 'compare' => $meta_compare,
+ ),
+ $relation,
+ ),
+ ) );
+
+ $vars['post_mime_type'] = imagify_get_mime_types();
+
+ return $vars;
+}
diff --git a/wp-content/plugins/imagify/inc/classes/Dependencies/.gitkeep b/wp-content/plugins/imagify/inc/classes/Dependencies/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/wp-content/plugins/imagify/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-async-request.php b/wp-content/plugins/imagify/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-async-request.php
new file mode 100644
index 00000000..3728718a
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-async-request.php
@@ -0,0 +1,181 @@
+identifier = $this->prefix . '_' . $this->action;
+
+ add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
+ add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
+ }
+
+ /**
+ * Set data used during the request
+ *
+ * @param array $data Data.
+ *
+ * @return $this
+ */
+ public function data( $data ) {
+ $this->data = $data;
+
+ return $this;
+ }
+
+ /**
+ * Dispatch the async request
+ *
+ * @return array|WP_Error
+ */
+ public function dispatch() {
+ $url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
+ $args = $this->get_post_args();
+
+ return wp_remote_post( esc_url_raw( $url ), $args );
+ }
+
+ /**
+ * Get query args
+ *
+ * @return array
+ */
+ protected function get_query_args() {
+ if ( property_exists( $this, 'query_args' ) ) {
+ return $this->query_args;
+ }
+
+ $args = array(
+ 'action' => $this->identifier,
+ 'nonce' => wp_create_nonce( $this->identifier ),
+ );
+
+ /**
+ * Filters the post arguments used during an async request.
+ *
+ * @param array $url
+ */
+ return apply_filters( $this->identifier . '_query_args', $args );
+ }
+
+ /**
+ * Get query URL
+ *
+ * @return string
+ */
+ protected function get_query_url() {
+ if ( property_exists( $this, 'query_url' ) ) {
+ return $this->query_url;
+ }
+
+ $url = admin_url( 'admin-ajax.php' );
+
+ /**
+ * Filters the post arguments used during an async request.
+ *
+ * @param string $url
+ */
+ return apply_filters( $this->identifier . '_query_url', $url );
+ }
+
+ /**
+ * Get post args
+ *
+ * @return array
+ */
+ protected function get_post_args() {
+ if ( property_exists( $this, 'post_args' ) ) {
+ return $this->post_args;
+ }
+
+ $args = array(
+ 'timeout' => 0.01,
+ 'blocking' => false,
+ 'body' => $this->data,
+ 'cookies' => $_COOKIE,
+ 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
+ );
+
+ /**
+ * Filters the post arguments used during an async request.
+ *
+ * @param array $args
+ */
+ return apply_filters( $this->identifier . '_post_args', $args );
+ }
+
+ /**
+ * Maybe handle
+ *
+ * Check for correct nonce and pass to handler.
+ */
+ public function maybe_handle() {
+ // Don't lock up other requests while processing
+ session_write_close();
+
+ check_ajax_referer( $this->identifier, 'nonce' );
+
+ $this->handle();
+
+ wp_die();
+ }
+
+ /**
+ * Handle
+ *
+ * Override this method to perform any actions required
+ * during the async request.
+ */
+ abstract protected function handle();
+
+}
diff --git a/wp-content/plugins/imagify/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-background-process.php b/wp-content/plugins/imagify/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-background-process.php
new file mode 100644
index 00000000..3928be66
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-background-process.php
@@ -0,0 +1,505 @@
+cron_hook_identifier = $this->identifier . '_cron';
+ $this->cron_interval_identifier = $this->identifier . '_cron_interval';
+
+ add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
+ add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
+ }
+
+ /**
+ * Dispatch
+ *
+ * @access public
+ * @return void
+ */
+ public function dispatch() {
+ // Schedule the cron healthcheck.
+ $this->schedule_event();
+
+ // Perform remote post.
+ return parent::dispatch();
+ }
+
+ /**
+ * Push to queue
+ *
+ * @param mixed $data Data.
+ *
+ * @return $this
+ */
+ public function push_to_queue( $data ) {
+ $this->data[] = $data;
+
+ return $this;
+ }
+
+ /**
+ * Save queue
+ *
+ * @return $this
+ */
+ public function save() {
+ $key = $this->generate_key();
+
+ if ( ! empty( $this->data ) ) {
+ update_site_option( $key, $this->data );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Update queue
+ *
+ * @param string $key Key.
+ * @param array $data Data.
+ *
+ * @return $this
+ */
+ public function update( $key, $data ) {
+ if ( ! empty( $data ) ) {
+ update_site_option( $key, $data );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Delete queue
+ *
+ * @param string $key Key.
+ *
+ * @return $this
+ */
+ public function delete( $key ) {
+ delete_site_option( $key );
+
+ return $this;
+ }
+
+ /**
+ * Generate key
+ *
+ * Generates a unique key based on microtime. Queue items are
+ * given a unique key so that they can be merged upon save.
+ *
+ * @param int $length Length.
+ *
+ * @return string
+ */
+ protected function generate_key( $length = 64 ) {
+ $unique = md5( microtime() . rand() );
+ $prepend = $this->identifier . '_batch_';
+
+ return substr( $prepend . $unique, 0, $length );
+ }
+
+ /**
+ * Maybe process queue
+ *
+ * Checks whether data exists within the queue and that
+ * the process is not already running.
+ */
+ public function maybe_handle() {
+ // Don't lock up other requests while processing
+ session_write_close();
+
+ if ( $this->is_process_running() ) {
+ // Background process already running.
+ wp_die();
+ }
+
+ if ( $this->is_queue_empty() ) {
+ // No data to process.
+ wp_die();
+ }
+
+ check_ajax_referer( $this->identifier, 'nonce' );
+
+ $this->handle();
+
+ wp_die();
+ }
+
+ /**
+ * Is queue empty
+ *
+ * @return bool
+ */
+ protected function is_queue_empty() {
+ global $wpdb;
+
+ $table = $wpdb->options;
+ $column = 'option_name';
+
+ if ( is_multisite() ) {
+ $table = $wpdb->sitemeta;
+ $column = 'meta_key';
+ }
+
+ $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
+
+ $count = $wpdb->get_var( $wpdb->prepare( "
+ SELECT COUNT(*)
+ FROM {$table}
+ WHERE {$column} LIKE %s
+ ", $key ) );
+
+ return ( $count > 0 ) ? false : true;
+ }
+
+ /**
+ * Is process running
+ *
+ * Check whether the current process is already running
+ * in a background process.
+ */
+ protected function is_process_running() {
+ if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
+ // Process already running.
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Lock process
+ *
+ * Lock the process so that multiple instances can't run simultaneously.
+ * Override if applicable, but the duration should be greater than that
+ * defined in the time_exceeded() method.
+ */
+ protected function lock_process() {
+ $this->start_time = time(); // Set start time of current process.
+
+ $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
+ $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
+
+ set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
+ }
+
+ /**
+ * Unlock process
+ *
+ * Unlock the process so that other instances can spawn.
+ *
+ * @return $this
+ */
+ protected function unlock_process() {
+ delete_site_transient( $this->identifier . '_process_lock' );
+
+ return $this;
+ }
+
+ /**
+ * Get batch
+ *
+ * @return stdClass Return the first batch from the queue
+ */
+ protected function get_batch() {
+ global $wpdb;
+
+ $table = $wpdb->options;
+ $column = 'option_name';
+ $key_column = 'option_id';
+ $value_column = 'option_value';
+
+ if ( is_multisite() ) {
+ $table = $wpdb->sitemeta;
+ $column = 'meta_key';
+ $key_column = 'meta_id';
+ $value_column = 'meta_value';
+ }
+
+ $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
+
+ $query = $wpdb->get_row( $wpdb->prepare( "
+ SELECT *
+ FROM {$table}
+ WHERE {$column} LIKE %s
+ ORDER BY {$key_column} ASC
+ LIMIT 1
+ ", $key ) );
+
+ $batch = new stdClass();
+ $batch->key = $query->$column;
+ $batch->data = maybe_unserialize( $query->$value_column );
+
+ return $batch;
+ }
+
+ /**
+ * Handle
+ *
+ * Pass each queue item to the task handler, while remaining
+ * within server memory and time limit constraints.
+ */
+ protected function handle() {
+ $this->lock_process();
+
+ do {
+ $batch = $this->get_batch();
+
+ foreach ( $batch->data as $key => $value ) {
+ $task = $this->task( $value );
+
+ if ( false !== $task ) {
+ $batch->data[ $key ] = $task;
+ } else {
+ unset( $batch->data[ $key ] );
+ }
+
+ if ( $this->time_exceeded() || $this->memory_exceeded() ) {
+ // Batch limits reached.
+ break;
+ }
+ }
+
+ // Update or delete current batch.
+ if ( ! empty( $batch->data ) ) {
+ $this->update( $batch->key, $batch->data );
+ } else {
+ $this->delete( $batch->key );
+ }
+ } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
+
+ $this->unlock_process();
+
+ // Start next batch or complete process.
+ if ( ! $this->is_queue_empty() ) {
+ $this->dispatch();
+ } else {
+ $this->complete();
+ }
+
+ wp_die();
+ }
+
+ /**
+ * Memory exceeded
+ *
+ * Ensures the batch process never exceeds 90%
+ * of the maximum WordPress memory.
+ *
+ * @return bool
+ */
+ protected function memory_exceeded() {
+ $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
+ $current_memory = memory_get_usage( true );
+ $return = false;
+
+ if ( $current_memory >= $memory_limit ) {
+ $return = true;
+ }
+
+ return apply_filters( $this->identifier . '_memory_exceeded', $return );
+ }
+
+ /**
+ * Get memory limit
+ *
+ * @return int
+ */
+ protected function get_memory_limit() {
+ if ( function_exists( 'ini_get' ) ) {
+ $memory_limit = ini_get( 'memory_limit' );
+ } else {
+ // Sensible default.
+ $memory_limit = '128M';
+ }
+
+ if ( ! $memory_limit || - 1 === intval( $memory_limit ) ) {
+ // Unlimited, set to 32GB.
+ $memory_limit = '32000M';
+ }
+
+ return wp_convert_hr_to_bytes( $memory_limit );
+ }
+
+ /**
+ * Time exceeded.
+ *
+ * Ensures the batch never exceeds a sensible time limit.
+ * A timeout limit of 30s is common on shared hosting.
+ *
+ * @return bool
+ */
+ protected function time_exceeded() {
+ $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
+ $return = false;
+
+ if ( time() >= $finish ) {
+ $return = true;
+ }
+
+ return apply_filters( $this->identifier . '_time_exceeded', $return );
+ }
+
+ /**
+ * Complete.
+ *
+ * Override if applicable, but ensure that the below actions are
+ * performed, or, call parent::complete().
+ */
+ protected function complete() {
+ // Unschedule the cron healthcheck.
+ $this->clear_scheduled_event();
+ }
+
+ /**
+ * Schedule cron healthcheck
+ *
+ * @access public
+ *
+ * @param mixed $schedules Schedules.
+ *
+ * @return mixed
+ */
+ public function schedule_cron_healthcheck( $schedules ) {
+ $interval = apply_filters( $this->identifier . '_cron_interval', 5 );
+
+ if ( property_exists( $this, 'cron_interval' ) ) {
+ $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
+ }
+
+ // Adds every 5 minutes to the existing schedules.
+ $schedules[ $this->identifier . '_cron_interval' ] = array(
+ 'interval' => MINUTE_IN_SECONDS * $interval,
+ 'display' => sprintf( __( 'Every %d Minutes' ), $interval ),
+ );
+
+ return $schedules;
+ }
+
+ /**
+ * Handle cron healthcheck
+ *
+ * Restart the background process if not already running
+ * and data exists in the queue.
+ */
+ public function handle_cron_healthcheck() {
+ if ( $this->is_process_running() ) {
+ // Background process already running.
+ exit;
+ }
+
+ if ( $this->is_queue_empty() ) {
+ // No data to process.
+ $this->clear_scheduled_event();
+ exit;
+ }
+
+ $this->handle();
+
+ exit;
+ }
+
+ /**
+ * Schedule event
+ */
+ protected function schedule_event() {
+ if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
+ wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
+ }
+ }
+
+ /**
+ * Clear scheduled event
+ */
+ protected function clear_scheduled_event() {
+ $timestamp = wp_next_scheduled( $this->cron_hook_identifier );
+
+ if ( $timestamp ) {
+ wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
+ }
+ }
+
+ /**
+ * Cancel Process
+ *
+ * Stop processing queue items, clear cronjob and delete batch.
+ *
+ */
+ public function cancel_process() {
+ if ( ! $this->is_queue_empty() ) {
+ $batch = $this->get_batch();
+
+ $this->delete( $batch->key );
+
+ wp_clear_scheduled_hook( $this->cron_hook_identifier );
+ }
+
+ }
+
+ /**
+ * Task
+ *
+ * Override this method to perform any actions required on each
+ * queue item. Return the modified item for further processing
+ * in the next pass through. Or, return false to remove the
+ * item from the queue.
+ *
+ * @param mixed $item Queue item to iterate over.
+ *
+ * @return mixed
+ */
+ abstract protected function task( $item );
+
+}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-background-process.php b/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-background-process.php
new file mode 100644
index 00000000..be177c70
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-background-process.php
@@ -0,0 +1,211 @@
+save()
+ * @see $this->maybe_save_and_dispatch()
+ * @author Grégory Viguier
+ */
+ protected $auto_dispatch = false;
+
+ /**
+ * The single instance of the class.
+ *
+ * @var object
+ * @since 1.8.1
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected static $_instance;
+
+
+ /**
+ * Get the main Instance.
+ *
+ * @since 1.8.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return object Main instance.
+ */
+ public static function get_instance() {
+ if ( ! isset( static::$_instance ) ) {
+ static::$_instance = new static();
+ }
+
+ return static::$_instance;
+ }
+
+ /**
+ * Init: launch a hook that will clear the scheduled events and empty the queue when the plugin is disabled.
+ * This is only a precaution in case something went wrong.
+ *
+ * @since 1.8.1
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function init() {
+ $this->query_url = admin_url( 'admin-ajax.php' );
+
+ /**
+ * Filter the URL to use for background processes.
+ *
+ * @since 1.9.5
+ * @author Grégory Viguier
+ *
+ * @param string $query_url An URL.
+ * @param object $this This class instance.
+ */
+ $this->query_url = apply_filters( 'imagify_background_process_url', $this->query_url, $this );
+
+ if ( ! $this->query_url || ! is_string( $this->query_url ) || ! preg_match( '@^https?://@', $this->query_url ) ) {
+ $this->query_url = admin_url( 'admin-ajax.php' );
+ }
+
+ // Deactivation hook.
+ if ( did_action( static::get_deactivation_hook_name() ) ) {
+ $this->cancel_process();
+ } else {
+ add_action( static::get_deactivation_hook_name(), [ $this, 'cancel_process' ] );
+ }
+
+ // Automatically save and dispatch at the end of the page if the queue is not empty.
+ add_action( 'shutdown', [ $this, 'maybe_save_and_dispatch' ], 666 ); // Evil magic number.
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OVERRIDES =============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Cancel Process.
+ * Stop processing queue items, clear cronjob and delete batch.
+ * This is a copy of the parent's method, in case an older version of WP_Background_Process is loaded instead of this one (an old version without this method).
+ *
+ * @since 1.8.1
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function cancel_process() {
+ if ( method_exists( $this, 'cancel_process' ) ) {
+ parent::cancel_process();
+ return;
+ }
+
+ if ( ! $this->is_queue_empty() ) {
+ $batch = $this->get_batch();
+
+ $this->delete( $batch->key );
+
+ wp_clear_scheduled_hook( $this->get_event_name() );
+ }
+ }
+
+ /**
+ * Save the queen. No, I meant the queue.
+ * Also empty the queue to avoid to create several batches with the same items.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return $this
+ */
+ public function save() {
+ if ( empty( $this->data ) ) {
+ return $this;
+ }
+
+ parent::save();
+
+ $this->auto_dispatch = true;
+ $this->data = [];
+
+ return $this;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Save and dispatch if the queue is not empty.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function maybe_save_and_dispatch() {
+ $this->save();
+
+ if ( $this->auto_dispatch ) {
+ $this->dispatch();
+ }
+ }
+
+ /**
+ * Get the cron name.
+ *
+ * @since 1.8.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_event_name() {
+ return $this->cron_hook_identifier;
+ }
+
+ /**
+ * Get the deactivation hook name.
+ *
+ * @since 1.8.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public static function get_deactivation_hook_name() {
+ static $deactivation_hook;
+
+ if ( ! isset( $deactivation_hook ) ) {
+ $deactivation_hook = 'deactivate_' . plugin_basename( IMAGIFY_FILE );
+ }
+
+ return $deactivation_hook;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-cron.php b/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-cron.php
new file mode 100644
index 00000000..28bddf08
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-cron.php
@@ -0,0 +1,299 @@
+get_event_name(), array( $this, 'do_event' ) );
+ add_filter( 'cron_schedules', array( $this, 'maybe_add_recurrence' ) );
+
+ if ( did_action( static::get_deactivation_hook_name() ) ) {
+ $this->unschedule_event();
+ } else {
+ add_action( static::get_deactivation_hook_name(), array( $this, 'unschedule_event' ) );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** HOOKS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Initiate the event.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function schedule_event() {
+ if ( ! wp_next_scheduled( $this->get_event_name() ) ) {
+ wp_schedule_event( $this->get_event_timestamp(), $this->get_event_recurrence(), $this->get_event_name() );
+ }
+ }
+
+ /**
+ * The event action.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ abstract public function do_event();
+
+ /**
+ * Unschedule the event at plugin or submodule deactivation.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function unschedule_event() {
+ wp_clear_scheduled_hook( $this->get_event_name() );
+ }
+
+ /**
+ * Add the event recurrence schedule.
+ *
+ * @since 1.7
+ * @access public
+ * @see wp_get_schedules()
+ * @author Grégory Viguier
+ *
+ * @param array $schedules An array of non-default cron schedules. Default empty.
+ *
+ * @return array
+ */
+ public function maybe_add_recurrence( $schedules ) {
+ $default_schedules = array(
+ 'hourly' => 1,
+ 'twicedaily' => 1,
+ 'daily' => 1,
+ );
+
+ $event_recurrence = $this->get_event_recurrence();
+
+ if ( ! empty( $schedules[ $event_recurrence ] ) || ! empty( $default_schedules[ $event_recurrence ] ) ) {
+ return $schedules;
+ }
+
+ $recurrences = array(
+ 'weekly' => array(
+ 'interval' => WEEK_IN_SECONDS,
+ 'display' => __( 'Once Weekly', 'imagify' ),
+ ),
+ );
+
+ if ( method_exists( $this, 'get_event_recurrence_attributes' ) ) {
+ $recurrences[ $event_recurrence ] = $this->get_event_recurrence_attributes();
+ }
+
+ if ( ! empty( $recurrences[ $event_recurrence ] ) ) {
+ $schedules[ $event_recurrence ] = $recurrences[ $event_recurrence ];
+ }
+
+ return $schedules;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the cron name.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_event_name() {
+ return $this->event_name;
+ }
+
+ /**
+ * Get the cron recurrence.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_event_recurrence() {
+ /**
+ * Filter the recurrence of the event.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param string $event_recurrence The recurrence.
+ * @param string $event_name Name of the event this recurrence is used for.
+ */
+ return apply_filters( 'imagify_event_recurrence', $this->event_recurrence, $this->get_event_name() );
+ }
+
+ /**
+ * Get the time to schedule the event.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_event_time() {
+ /**
+ * Filter the time at which the event is triggered (WordPress time).
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param string $event_time A 24H formated time: `hour:minute`.
+ * @param string $event_name Name of the event this time is used for.
+ */
+ return apply_filters( 'imagify_event_time', $this->event_time, $this->get_event_name() );
+ }
+
+ /**
+ * Get the timestamp to schedule the event.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int Timestamp.
+ */
+ public function get_event_timestamp() {
+ return self::get_next_timestamp( $this->get_event_time() );
+ }
+
+ /**
+ * Get the timestamp of the next (event) date for the given hour.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @source secupress_get_next_cron_timestamp()
+ *
+ * @param string $event_time Time when the event callback should be triggered (WordPress time), formated like `hh:mn` (hour:minute).
+ *
+ * @return int Timestamp.
+ */
+ public static function get_next_timestamp( $event_time = '00:00' ) {
+ $current_time_int = (int) date( 'Gis' );
+ $event_time_int = (int) str_replace( ':', '', $event_time . '00' );
+ $event_time = explode( ':', $event_time );
+ $event_hour = (int) $event_time[0];
+ $event_minute = (int) $event_time[1];
+ $offset = get_option( 'gmt_offset' ) * HOUR_IN_SECONDS;
+
+ if ( $event_time_int <= $current_time_int ) {
+ // The event time is passed, we need to schedule the event tomorrow.
+ return mktime( $event_hour, $event_minute, 0, (int) date( 'n' ), (int) date( 'j' ) + 1 ) - $offset;
+ }
+
+ // We haven't passed the event time yet, schedule the event today.
+ return mktime( $event_hour, $event_minute, 0 ) - $offset;
+ }
+
+ /**
+ * Get the deactivation hook name.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public static function get_deactivation_hook_name() {
+ static $deactivation_hook;
+
+ if ( ! isset( $deactivation_hook ) ) {
+ $deactivation_hook = 'deactivate_' . plugin_basename( IMAGIFY_FILE );
+ }
+
+ return $deactivation_hook;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-db.php b/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-db.php
new file mode 100644
index 00000000..f4e6a9a0
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-db.php
@@ -0,0 +1,883 @@
+table_is_global ? $wpdb->base_prefix : $wpdb->prefix;
+
+ $this->table_name = $prefix . $this->table;
+
+ if ( ! $this->table_is_up_to_date() ) {
+ /**
+ * The option doesn't exist or is not up-to-date: we must upgrade the table before declaring it ready.
+ * See self::maybe_upgrade_table() for the upgrade.
+ */
+ return;
+ }
+
+ $this->set_table_ready();
+ }
+
+ /**
+ * Get the main Instance.
+ *
+ * @since 1.6.5
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return object Main instance.
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$_instance ) ) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Init:
+ * - Launch hooks.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function init() {
+ add_action( 'admin_init', array( $this, 'maybe_upgrade_table' ) );
+ }
+
+ /**
+ * Tell if we can work with the tables.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function can_operate() {
+ return $this->table_created;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TABLE SPECIFICS ========================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the column placeholders.
+ *
+ * @since 1.5
+ * @access public
+ *
+ * @return array
+ */
+ abstract public function get_columns();
+
+ /**
+ * Default column values.
+ *
+ * @since 1.5
+ * @access public
+ *
+ * @return array
+ */
+ abstract public function get_column_defaults();
+
+ /**
+ * Get the query to create the table fields.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ abstract protected function get_table_schema();
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** QUERIES ================================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if the table is empty or not.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool True if the table contains at least one row.
+ */
+ public function has_items() {
+ global $wpdb;
+
+ $column = esc_sql( $this->primary_key );
+
+ return (bool) $wpdb->get_var( "SELECT $column FROM $this->table_name LIMIT 1;" ); // WPCS: unprepared SQL ok.
+ }
+
+ /**
+ * Retrieve a row by the primary key.
+ *
+ * @since 1.5
+ * @access public
+ *
+ * @param string $row_id A primary key.
+ * @return array
+ */
+ public function get( $row_id ) {
+ if ( $row_id <= 0 ) {
+ return array();
+ }
+
+ return $this->get_by( $this->primary_key, $row_id );
+ }
+
+ /**
+ * Retrieve a row by a specific column / value.
+ *
+ * @since 1.5
+ * @access public
+ *
+ * @param string $column_where A column name.
+ * @param mixed $column_value A value.
+ * @return array
+ */
+ public function get_by( $column_where, $column_value ) {
+ global $wpdb;
+
+ $placeholder = $this->get_placeholder( $column_where );
+ $column_where = esc_sql( $column_where );
+
+ $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $column_where = $placeholder LIMIT 1;", $column_value ), ARRAY_A ); // WPCS: unprepared SQL ok, PreparedSQLPlaceholders replacement count ok.
+
+ return (array) $this->cast_row( $result );
+ }
+
+ /**
+ * Retrieve a row by the specified column / values.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_where A column name.
+ * @param array $column_values An array of values.
+ * @return array
+ */
+ public function get_in( $column_where, $column_values ) {
+ global $wpdb;
+
+ $column_where = esc_sql( $column_where );
+ $column_values = Imagify_DB::prepare_values_list( $column_values );
+
+ $result = $wpdb->get_row( "SELECT * FROM $this->table_name WHERE $column_where IN ( $column_values ) LIMIT 1;", ARRAY_A ); // WPCS: unprepared SQL ok.
+
+ return (array) $this->cast_row( $result );
+ }
+
+ /**
+ * Retrieve a var by the primary key.
+ * Previously named get_column().
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @param string $row_id A primary key.
+ * @return mixed
+ */
+ public function get_var( $column_select, $row_id ) {
+ if ( $row_id <= 0 ) {
+ return false;
+ }
+
+ return $this->get_var_by( $column_select, $this->primary_key, $row_id );
+ }
+
+ /**
+ * Retrieve a var by the specified column / value.
+ * Previously named get_column_by().
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @param string $column_where A column name.
+ * @param string $column_value A value.
+ * @return mixed
+ */
+ public function get_var_by( $column_select, $column_where, $column_value ) {
+ global $wpdb;
+
+ $placeholder = $this->get_placeholder( $column_where );
+ $column = esc_sql( $column_select );
+ $column_where = esc_sql( $column_where );
+
+ $result = $wpdb->get_var( $wpdb->prepare( "SELECT $column FROM $this->table_name WHERE $column_where = $placeholder LIMIT 1;", $column_value ) ); // WPCS: unprepared SQL ok, PreparedSQLPlaceholders replacement count ok.
+
+ return $this->cast( $result, $column_select );
+ }
+
+ /**
+ * Retrieve a var by the specified column / values.
+ * Previously named get_column_in().
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @param string $column_where A column name.
+ * @param array $column_values An array of values.
+ * @return mixed
+ */
+ public function get_var_in( $column_select, $column_where, $column_values ) {
+ global $wpdb;
+
+ $column = esc_sql( $column_select );
+ $column_where = esc_sql( $column_where );
+ $column_values = Imagify_DB::prepare_values_list( $column_values );
+
+ $result = $wpdb->get_var( "SELECT $column FROM $this->table_name WHERE $column_where IN ( $column_values ) LIMIT 1;" ); // WPCS: unprepared SQL ok.
+
+ return $this->cast( $result, $column_select );
+ }
+
+ /**
+ * Retrieve values by the specified column / values.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @param string $column_where A column name.
+ * @param array $column_values An array of values.
+ * @return array
+ */
+ public function get_column_in( $column_select, $column_where, $column_values ) {
+ global $wpdb;
+
+ $column = esc_sql( $column_select );
+ $column_where = esc_sql( $column_where );
+ $column_values = Imagify_DB::prepare_values_list( $column_values );
+
+ $result = $wpdb->get_col( "SELECT $column FROM $this->table_name WHERE $column_where IN ( $column_values );" ); // WPCS: unprepared SQL ok.
+
+ return $this->cast_col( $result, $column_select );
+ }
+
+ /**
+ * Retrieve values by the specified column / values.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @param string $column_where A column name.
+ * @param array $column_values An array of values.
+ * @return array
+ */
+ public function get_column_not_in( $column_select, $column_where, $column_values ) {
+ global $wpdb;
+
+ $column = esc_sql( $column_select );
+ $column_where = esc_sql( $column_where );
+ $column_values = Imagify_DB::prepare_values_list( $column_values );
+
+ $result = $wpdb->get_col( "SELECT $column FROM $this->table_name WHERE $column_where NOT IN ( $column_values );" ); // WPCS: unprepared SQL ok.
+
+ return $this->cast_col( $result, $column_select );
+ }
+
+ /**
+ * Insert a new row.
+ *
+ * @since 1.5
+ * @access public
+ *
+ * @param string $data New data.
+ * @return int The ID.
+ */
+ public function insert( $data ) {
+ global $wpdb;
+
+ // Initialise column format array.
+ $column_formats = $this->get_columns();
+
+ // Set default values.
+ $data = wp_parse_args( $data, $this->get_column_defaults() );
+
+ // Force fields to lower case.
+ $data = array_change_key_case( $data );
+
+ // White list columns.
+ $data = array_intersect_key( $data, $column_formats );
+
+ // Maybe serialize some values.
+ $data = $this->serialize_columns( $data );
+
+ // Reorder $column_formats to match the order of columns given in $data.
+ $column_formats = array_merge( $data, $column_formats );
+
+ $wpdb->insert( $this->table_name, $data, $column_formats );
+
+ return (int) $wpdb->insert_id;
+ }
+
+ /**
+ * Update a row.
+ *
+ * @since 1.5
+ * @access public
+ *
+ * @param int $row_id A primary key.
+ * @param array $data New data.
+ * @param string $where A column name.
+ * @return bool
+ */
+ public function update( $row_id, $data = array(), $where = '' ) {
+ global $wpdb;
+
+ if ( $row_id <= 0 ) {
+ return false;
+ }
+
+ if ( ! $this->get( $row_id ) ) {
+ $this->insert( $data );
+ return true;
+ }
+
+ if ( empty( $where ) ) {
+ $where = $this->primary_key;
+ }
+
+ // Initialise column format array.
+ $column_formats = $this->get_columns();
+
+ // Force fields to lower case.
+ $data = array_change_key_case( $data );
+
+ // White list columns.
+ $data = array_intersect_key( $data, $column_formats );
+
+ // Maybe serialize some values.
+ $data = $this->serialize_columns( $data );
+
+ // Reorder $column_formats to match the order of columns given in $data.
+ $column_formats = array_merge( $data, $column_formats );
+
+ return (bool) $wpdb->update( $this->table_name, $data, array( $where => $row_id ), $column_formats, $this->get_placeholder( $where ) );
+ }
+
+ /**
+ * Delete a row identified by the primary key.
+ *
+ * @since 1.5
+ * @access public
+ *
+ * @param string $row_id A primary key.
+ * @return bool
+ */
+ public function delete( $row_id = 0 ) {
+ global $wpdb;
+
+ if ( $row_id <= 0 ) {
+ return false;
+ }
+
+ $placeholder = $this->get_placeholder( $this->primary_key );
+
+ return (bool) $wpdb->query( $wpdb->prepare( "DELETE FROM $this->table_name WHERE $this->primary_key = $placeholder", $row_id ) ); // WPCS: unprepared SQL ok, PreparedSQLPlaceholders replacement count ok.
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TABLE CREATION ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Maybe create/upgrade the table in the database.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function maybe_upgrade_table() {
+ global $wpdb;
+
+ if ( $this->table_is_up_to_date() ) {
+ // The table has the right version.
+ $this->set_table_ready();
+ return;
+ }
+
+ // Create the table.
+ $this->create_table();
+ }
+
+ /**
+ * Create/Upgrade the table in the database.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function create_table() {
+ if ( ! Imagify_DB::create_table( $this->get_table_name(), $this->get_table_schema() ) ) {
+ // Failure.
+ $this->set_table_not_ready();
+ $this->delete_db_version();
+ return;
+ }
+
+ // Table successfully created/upgraded.
+ $this->set_table_ready();
+ $this->update_db_version();
+ }
+
+ /**
+ * Set various properties to tell the table is ready to be used.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ protected function set_table_ready() {
+ global $wpdb;
+
+ $this->table_created = true;
+ $wpdb->{$this->table} = $this->table_name;
+
+ if ( $this->table_is_global ) {
+ $wpdb->global_tables[] = $this->table;
+ } else {
+ $wpdb->tables[] = $this->table;
+ }
+ }
+
+ /**
+ * Unset various properties to tell the table is NOT ready to be used.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ protected function set_table_not_ready() {
+ global $wpdb;
+
+ $this->table_created = false;
+ unset( $wpdb->{$this->table} );
+
+ if ( $this->table_is_global ) {
+ $wpdb->global_tables = array_diff( $wpdb->global_tables, array( $this->table ) );
+ } else {
+ $wpdb->tables = array_diff( $wpdb->tables, array( $this->table ) );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TABLE VERSION =========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the table version.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int
+ */
+ public function get_table_version() {
+ return $this->table_version;
+ }
+
+ /**
+ * Tell if the table is up-to-date (we don't "downgrade" the tables).
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function table_is_up_to_date() {
+ return $this->get_db_version() >= $this->get_table_version();
+ }
+
+ /**
+ * Get the table version stored in DB.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int|bool The version. False if not set yet.
+ */
+ public function get_db_version() {
+ $option_name = $this->table . self::TABLE_VERSION_OPTION_SUFFIX;
+
+ if ( $this->table_is_global && is_multisite() ) {
+ return get_site_option( $option_name );
+ }
+
+ return get_option( $option_name );
+ }
+
+ /**
+ * Update the table version stored in DB.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected function update_db_version() {
+ $option_name = $this->table . self::TABLE_VERSION_OPTION_SUFFIX;
+
+ if ( $this->table_is_global && is_multisite() ) {
+ update_site_option( $option_name, $this->get_table_version() );
+ } else {
+ update_option( $option_name, $this->get_table_version() );
+ }
+ }
+
+ /**
+ * Delete the table version stored in DB.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected function delete_db_version() {
+ $option_name = $this->table . self::TABLE_VERSION_OPTION_SUFFIX;
+
+ if ( $this->table_is_global && is_multisite() ) {
+ delete_site_option( $option_name );
+ } else {
+ delete_option( $option_name );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the table name.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_table_name() {
+ return $this->table_name;
+ }
+
+ /**
+ * Tell if the table is the same for each site of a Multisite.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_table_global() {
+ return $this->table_is_global;
+ }
+
+ /**
+ * Get the primary column name.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_primary_key() {
+ return $this->primary_key;
+ }
+
+ /**
+ * Get the formats related to the given columns.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $columns An array of column names (as keys).
+ * @return array
+ */
+ public function get_column_formats( $columns ) {
+ if ( ! is_array( $columns ) ) {
+ $columns = array_flip( (array) $columns );
+ }
+
+ // White list columns.
+ return array_intersect_key( $this->get_columns(), $columns );
+ }
+
+ /**
+ * Get the placeholder corresponding to the given key.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $key The key.
+ * @return string
+ */
+ public function get_placeholder( $key ) {
+ $columns = $this->get_columns();
+ return isset( $columns[ $key ] ) ? $columns[ $key ] : '%s';
+ }
+
+ /**
+ * Tell if the column value must be (un)serialized.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $key The key.
+ * @return bool
+ */
+ public function is_column_serialized( $key ) {
+ $columns = $this->get_column_defaults();
+ return isset( $columns[ $key ] ) && is_array( $columns[ $key ] );
+ }
+
+ /**
+ * Cast a value.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param mixed $value The value to cast.
+ * @param string $key The corresponding key.
+ * @return mixed
+ */
+ public function cast( $value, $key ) {
+ if ( null === $value || is_bool( $value ) ) {
+ return $value;
+ }
+
+ $placeholder = $this->get_placeholder( $key );
+
+ if ( '%d' === $placeholder ) {
+ return (int) $value;
+ }
+
+ if ( '%f' === $placeholder ) {
+ return (float) $value;
+ }
+
+ if ( $value && $this->is_column_serialized( $key ) ) {
+ return maybe_unserialize( $value );
+ }
+
+ return $value;
+ }
+
+ /**
+ * Cast a column.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $values The values to cast.
+ * @param string $column The corresponding column name.
+ * @return array
+ */
+ public function cast_col( $values, $column ) {
+ if ( ! $values ) {
+ return $values;
+ }
+
+ foreach ( $values as $i => $value ) {
+ $values[ $i ] = $this->cast( $value, $column );
+ }
+
+ return $values;
+ }
+
+ /**
+ * Cast a row.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array|object $row_fields A row from the DB.
+ * @return array|object
+ */
+ public function cast_row( $row_fields ) {
+ if ( ! $row_fields ) {
+ return $row_fields;
+ }
+
+ if ( is_array( $row_fields ) ) {
+ foreach ( $row_fields as $field => $value ) {
+ $row_fields[ $field ] = $this->cast( $value, $field );
+ }
+ } elseif ( is_object( $row_fields ) ) {
+ foreach ( $row_fields as $field => $value ) {
+ $row_fields->$field = $this->cast( $value, $field );
+ }
+ }
+
+ return $row_fields;
+ }
+
+ /**
+ * Serialize columns that need to be.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $data An array of values.
+ * @return array
+ */
+ public function serialize_columns( $data ) {
+ if ( ! isset( $this->to_serialize ) ) {
+ $this->to_serialize = array_filter( $this->get_column_defaults(), 'is_array' );
+ }
+
+ if ( ! $this->to_serialize ) {
+ return $data;
+ }
+
+ $serialized_data = array_intersect_key( $data, $this->to_serialize );
+
+ if ( ! $serialized_data ) {
+ return $data;
+ }
+
+ $serialized_data = array_map( function( $array ) {
+ // Try not to store empty serialized arrays.
+ return [] === $array ? null : maybe_serialize( $array );
+ }, $serialized_data );
+
+ return array_merge( $data, $serialized_data );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-options.php b/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-options.php
new file mode 100644
index 00000000..2caeeaab
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-abstract-options.php
@@ -0,0 +1,623 @@
+hook_identifier = rtrim( strtolower( str_replace( 'Imagify_', '', get_class( $this ) ) ), 's' );
+
+ if ( ! is_string( $this->autoload ) ) {
+ $this->autoload = $this->autoload ? 'yes' : 'no';
+ }
+
+ $this->default_values = array_merge( array(
+ 'version' => '',
+ ), $this->default_values );
+ }
+
+ /**
+ * Get the main Instance.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return object Main instance.
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$_instance ) ) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Launch the hooks.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function init() {
+ add_filter( 'sanitize_option_' . $this->get_option_name(), array( $this, 'sanitize_and_validate_on_update' ), 50 );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** GET/SET/DELETE OPTION(S) ================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get an Imagify option.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $key The option name.
+ * @return mixed The option value.
+ */
+ public function get( $key ) {
+ $default_values = $this->get_default_values();
+
+ if ( ! isset( $default_values[ $key ] ) ) {
+ return null;
+ }
+
+ $default = $default_values[ $key ];
+
+ /**
+ * Pre-filter any Imagify option before read.
+ *
+ * @since 1.0
+ *
+ * @param mixed $value Value to return instead of the option value. Default null to skip it.
+ * @param mixed $default The default value.
+ */
+ $value = apply_filters( 'pre_get_imagify_' . $this->get_hook_identifier() . '_' . $key, null, $default );
+
+ if ( isset( $value ) ) {
+ return $value;
+ }
+
+ // Get all values.
+ $values = $this->get_all();
+
+ // Sanitize and validate the value.
+ $value = $this->sanitize_and_validate( $key, $values[ $key ], $default );
+
+ /**
+ * Filter any Imagify option after read.
+ *
+ * @since 1.0
+ *
+ * @param mixed $value Value of the option.
+ * @param mixed $default The default value. Default false.
+ */
+ return apply_filters( 'get_imagify_' . $this->get_hook_identifier() . '_' . $key, $value, $default );
+ }
+
+ /**
+ * Get all options (no cast, no sanitization, no validation).
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array The options.
+ */
+ public function get_all() {
+ $values = $this->get_raw();
+
+ if ( ! $values ) {
+ return $this->get_reset_values();
+ }
+
+ return imagify_merge_intersect( $values, $this->get_default_values() );
+ }
+
+ /**
+ * Set one or multiple options.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param array $values An array of option name / option value pairs.
+ */
+ public function set( $values ) {
+ $args = func_get_args();
+
+ if ( isset( $args[1] ) && is_string( $args[0] ) ) {
+ $values = array( $args[0] => $args[1] );
+ }
+
+ if ( ! is_array( $values ) ) {
+ // PABKAC.
+ return;
+ }
+
+ $values = array_merge( $this->get_all(), $values );
+ $values = array_intersect_key( $values, $this->get_default_values() );
+
+ $this->set_raw( $values );
+ }
+
+ /**
+ * Delete one or multiple options.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param array|string $keys An array of option names or a single option name.
+ */
+ public function delete( $keys ) {
+ $values = $this->get_raw();
+
+ if ( ! $values ) {
+ if ( false !== $values ) {
+ $this->delete_raw();
+ }
+ return;
+ }
+
+ $keys = array_flip( (array) $keys );
+ $values = array_diff_key( $values, $keys );
+
+ $this->set_raw( $values );
+ }
+
+ /**
+ * Checks if the option with the given name exists or not.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $key The option name.
+ * @return bool
+ */
+ public function has( $key ) {
+ return null !== $this->get( $key );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** GET / UPDATE / DELETE RAW VALUES ======================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the name of the option that stores the settings.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return string
+ */
+ public function get_option_name() {
+ return IMAGIFY_SLUG . '_' . $this->identifier;
+ }
+
+ /**
+ * Get the identifier used in the hook names.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return string
+ */
+ public function get_hook_identifier() {
+ return $this->hook_identifier;
+ }
+
+ /**
+ * Tell if the option is autoloaded.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_autoloaded() {
+ return 'yes' === $this->autoload;
+ }
+
+ /**
+ * Tell if the option is a network option.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_network_option() {
+ return (bool) $this->network_option;
+ }
+
+ /**
+ * Get the raw value of all Imagify options.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array|bool The options. False if not set yet. An empty array if invalid.
+ */
+ public function get_raw() {
+ $values = $this->is_network_option() ? get_site_option( $this->get_option_name() ) : get_option( $this->get_option_name() );
+
+ if ( false !== $values && ! is_array( $values ) ) {
+ return array();
+ }
+
+ return $values;
+ }
+
+ /**
+ * Update the Imagify options.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param array $values An array of option name / option value pairs.
+ */
+ public function set_raw( $values ) {
+ if ( ! $values ) {
+ // The option is empty: delete it.
+ $this->delete_raw();
+
+ } elseif ( $this->is_network_option() ) {
+ // Network option.
+ update_site_option( $this->get_option_name(), $values );
+
+ } elseif ( false === get_option( $this->get_option_name() ) ) {
+ // Compat' with WP < 4.2 + autoload: the option doesn't exist in the database.
+ add_option( $this->get_option_name(), $values, '', $this->autoload );
+ } else {
+ // Update the current value.
+ update_option( $this->get_option_name(), $values, $this->autoload );
+ }
+ }
+
+ /**
+ * Delete all Imagify options.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function delete_raw() {
+ $this->is_network_option() ? delete_site_option( $this->get_option_name() ) : delete_option( $this->get_option_name() );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** DEFAULT + RESET VALUES ================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get default option values.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array
+ */
+ public function get_default_values() {
+ $default_values = $this->default_values;
+
+ if ( ! empty( $default_values['cached'] ) ) {
+ unset( $default_values['cached'] );
+ return $default_values;
+ }
+
+ /**
+ * Allow to add more default option values.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $new_values New default option values.
+ * @param array $default_values Plugin default option values.
+ */
+ $new_values = apply_filters( 'imagify_default_' . $this->get_hook_identifier() . '_values', array(), $default_values );
+ $new_values = is_array( $new_values ) ? $new_values : array();
+
+ if ( $new_values ) {
+ // Don't allow new values to overwrite the plugin values.
+ $new_values = array_diff_key( $new_values, $default_values );
+ }
+
+ if ( $new_values ) {
+ $default_values = array_merge( $default_values, $new_values );
+ $this->default_values = $default_values;
+ }
+
+ $this->default_values['cached'] = 1;
+
+ return $default_values;
+ }
+
+ /**
+ * Get the values used when the option is empty.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array
+ */
+ public function get_reset_values() {
+ $reset_values = $this->reset_values;
+
+ if ( ! empty( $reset_values['cached'] ) ) {
+ unset( $reset_values['cached'] );
+ return $reset_values;
+ }
+
+ $default_values = $this->get_default_values();
+ $reset_values = array_merge( $default_values, $reset_values );
+
+ /**
+ * Allow to filter the "reset" option values.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $reset_values Plugin reset option values.
+ */
+ $new_values = apply_filters( 'imagify_reset_' . $this->get_hook_identifier() . '_values', $reset_values );
+
+ if ( $new_values && is_array( $new_values ) ) {
+ $reset_values = array_merge( $reset_values, $new_values );
+ }
+
+ $this->reset_values = $reset_values;
+ $this->reset_values['cached'] = 1;
+
+ return $reset_values;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** SANITIZATION, VALIDATION ================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Sanitize and validate an option value.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $key The option key.
+ * @param mixed $value The value.
+ * @param mixed $default The default value.
+ * @return mixed
+ */
+ public function sanitize_and_validate( $key, $value, $default = null ) {
+ if ( ! isset( $default ) ) {
+ $default_values = $this->get_default_values();
+ $default = $default_values[ $key ];
+ }
+
+ // Cast the value.
+ $value = self::cast( $value, $default );
+
+ if ( $value === $default ) {
+ return $value;
+ }
+
+ // Version.
+ if ( 'version' === $key ) {
+ return sanitize_text_field( $value );
+ }
+
+ return $this->sanitize_and_validate_value( $key, $value, $default );
+ }
+
+ /**
+ * Sanitize and validate an option value. Basic casts have been made.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $key The option key.
+ * @param mixed $value The value.
+ * @param mixed $default The default value.
+ * @return mixed
+ */
+ abstract public function sanitize_and_validate_value( $key, $value, $default );
+
+ /**
+ * Sanitize and validate Imagify's options before storing them.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $values The option value.
+ * @return array
+ */
+ public function sanitize_and_validate_on_update( $values ) {
+ $values = is_array( $values ) ? $values : array();
+ $default_values = $this->get_default_values();
+
+ if ( $values ) {
+ foreach ( $default_values as $key => $default ) {
+ if ( isset( $values[ $key ] ) ) {
+ $values[ $key ] = $this->sanitize_and_validate( $key, $values[ $key ], $default );
+ }
+ }
+ }
+
+ $values = array_intersect_key( $values, $default_values );
+
+ // Version.
+ if ( empty( $values['version'] ) ) {
+ $values['version'] = IMAGIFY_VERSION;
+ }
+
+ return $this->validate_values_on_update( $values );
+ }
+
+ /**
+ * Validate Imagify's options before storing them. Basic sanitization and validation have been made, row by row.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $values The option value.
+ * @return array
+ */
+ public function validate_values_on_update( $values ) {
+ return $values;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Cast a value, depending on its default value type.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param mixed $value The value to cast.
+ * @param mixed $default The default value.
+ * @return mixed
+ */
+ public static function cast( $value, $default ) {
+ if ( is_array( $default ) ) {
+ return is_array( $value ) ? $value : array();
+ }
+
+ if ( is_int( $default ) ) {
+ return (int) $value;
+ }
+
+ if ( is_bool( $default ) ) {
+ return (bool) $value;
+ }
+
+ if ( is_float( $default ) ) {
+ return round( (float) $value, 3 );
+ }
+
+ return $value;
+ }
+
+ /**
+ * Cast a float like 3.000 into an integer.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param float $float The float.
+ * @return float|int
+ */
+ public static function maybe_cast_float_as_int( $float ) {
+ return ( $float / (int) $float ) === (float) 1 ? (int) $float : $float;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-admin-ajax-post.php b/wp-content/plugins/imagify/inc/classes/class-imagify-admin-ajax-post.php
new file mode 100644
index 00000000..8dab2d51
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-admin-ajax-post.php
@@ -0,0 +1,1669 @@
+filesystem = Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Launch the hooks.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function init() {
+ if ( wp_doing_ajax() ) {
+ // Actions triggered only on admin ajax.
+ $actions = array_merge( $this->ajax_post_actions, $this->ajax_only_actions );
+
+ foreach ( $actions as $action ) {
+ add_action( 'wp_ajax_' . $action, array( $this, $action . '_callback' ) );
+ }
+ }
+
+ // Actions triggered on both admin ajax and admin post.
+ $actions = array_merge( $this->ajax_post_actions, $this->post_only_actions );
+
+ foreach ( $actions as $action ) {
+ add_action( 'admin_post_' . $action, array( $this, $action . '_callback' ) );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** OPTIMIZATION PROCESSES ================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Optimize one media.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param string $context The context.
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ protected function optimize_media( $media_id, $context ) {
+ return imagify_get_optimization_process( $media_id, $context )->optimize();
+ }
+
+ /**
+ * Re-optimize a media to a different optimization level.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param string $context The context.
+ * @param int $level The optimization level.
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ protected function reoptimize_media( $media_id, $context, $level ) {
+ return imagify_get_optimization_process( $media_id, $context )->reoptimize( $level );
+ }
+
+ /**
+ * Optimize all files from a media, whatever this mediaâs previous optimization status (will be restored if needed).
+ * This is used by the bulk optimization page.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param string $context The context.
+ * @param int $level The optimization level.
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ protected function force_optimize( $media_id, $context, $level ) {
+ $process = imagify_get_optimization_process( $media_id, $context );
+ $data = $process->get_data();
+
+ // Restore before re-optimizing.
+ if ( $data->is_optimized() ) {
+ $result = $process->restore();
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ return $result;
+ }
+ }
+
+ return $process->optimize( $level );
+ }
+
+ /**
+ * Optimize one or some thumbnails that are not optimized yet.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param string $context The context.
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ protected function optimize_missing_sizes( $media_id, $context ) {
+ return imagify_get_optimization_process( $media_id, $context )->optimize_missing_thumbnails();
+ }
+
+ /**
+ * Generate webp images if they are missing.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param string $context The context.
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ protected function generate_webp_versions( $media_id, $context ) {
+ return imagify_get_optimization_process( $media_id, $context )->generate_webp_versions();
+ }
+
+ /**
+ * Delete webp images for media that are "already_optimize".
+ *
+ * @since 1.9.6
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param string $context The context.
+ * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
+ */
+ protected function delete_webp_versions( $media_id, $context ) {
+ $process = imagify_get_optimization_process( $media_id, $context );
+
+ if ( ! $process->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $data = $process->get_data();
+
+ if ( ! $data->is_already_optimized() ) {
+ return new \WP_Error( 'not_already_optimized', __( 'This media does not have the right optimization status.', 'imagify' ) );
+ }
+
+ if ( ! $process->has_webp() ) {
+ return true;
+ }
+
+ $data->delete_optimization_data();
+ $deleted = $process->delete_webp_files();
+
+ if ( is_wp_error( $deleted ) ) {
+ return new \WP_Error( 'webp_not_deleted', __( 'Previous webp files could not be deleted.', 'imagify' ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * Restore a media.
+ *
+ * @since 1.9
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param string $context The context.
+ * @return bool|WP_Error True on success. A \WP_Error instance on failure.
+ */
+ protected function restore_media( $media_id, $context ) {
+ return imagify_get_optimization_process( $media_id, $context )->restore();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** BULK OPTIMIZATION CALLBACKS ============================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get media ids for the requested imagify bulk action.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_get_media_ids_callback() {
+ imagify_check_nonce( 'imagify-bulk-optimize' );
+
+ $context = $this->get_context();
+
+ if ( ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
+ imagify_die();
+ }
+
+ $bulk = $this->get_bulk_instance( $context );
+
+ switch ( $this->get_imagify_action() ) {
+ case 'optimize':
+ $this->check_can_optimize();
+ $data = $bulk->get_unoptimized_media_ids( $this->get_optimization_level() );
+ break;
+
+ case 'generate_webp':
+ $this->check_can_optimize();
+ $data = $bulk->get_optimized_media_ids_without_webp();
+
+ if ( ! $data['ids'] && $data['errors']['no_backup'] ) {
+ // No backup, no webp.
+ $data = 'no-backup';
+ } elseif ( ! $data['ids'] && $data['errors']['no_file_path'] ) {
+ // Error.
+ $data = __( 'The path to the selected files could not be retrieved.', 'imagify' );
+ } else {
+ // OK.
+ $data = $data['ids'];
+ }
+ break;
+
+ default:
+ $data = [];
+ }
+
+ if ( ! is_array( $data ) ) {
+ wp_send_json_error( [ 'message' => $data ] );
+ }
+
+ wp_send_json_success( $data );
+ }
+
+ /**
+ * Process a media with the requested imagify bulk action.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Jonathan Buttigieg
+ */
+ public function imagify_bulk_optimize_callback() {
+ imagify_check_nonce( 'imagify-bulk-optimize' );
+
+ $media_id = $this->get_media_id( 'POST', 'media_id' );
+ $context = $this->get_context( 'POST' );
+
+ if ( ! $media_id || ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize', $media_id ) ) {
+ imagify_die();
+ }
+
+ switch ( $this->get_imagify_action() ) {
+ case 'optimize':
+ $level = $this->get_optimization_level( 'POST' );
+ $result = $this->force_optimize( $media_id, $context, $level );
+ break;
+
+ case 'generate_webp':
+ $result = $this->generate_webp_versions( $media_id, $context );
+ break;
+
+ default:
+ $result = new \WP_Error( 'unknown_action', __( 'Unknown action', 'imagify' ) );
+ }
+
+ if ( is_wp_error( $result ) ) {
+ wp_send_json_error( [ 'error' => $result->get_error_message() ] );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Get stats data for a specific folder type.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_get_folder_type_data_callback() {
+ imagify_check_nonce( 'imagify-bulk-optimize' );
+
+ $context = $this->get_context();
+
+ if ( ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
+ imagify_die();
+ }
+
+ $bulk = $this->get_bulk_instance( $context );
+
+ wp_send_json_success( $bulk->get_context_data() );
+ }
+
+ /**
+ * Set the "bulk info" popup state as "seen".
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_bulk_info_seen_callback() {
+ imagify_check_nonce( 'imagify-bulk-optimize' );
+
+ $context = $this->get_context();
+
+ if ( ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
+ imagify_die();
+ }
+
+ set_transient( 'imagify_bulk_optimization_infos', 1, WEEK_IN_SECONDS );
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Get generic stats to display in the bulk page.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_bulk_get_stats_callback() {
+ imagify_check_nonce( 'imagify-bulk-optimize' );
+
+ $folder_types = filter_input( INPUT_GET, 'types', FILTER_SANITIZE_STRING, FILTER_REQUIRE_ARRAY );
+ $folder_types = is_array( $folder_types ) ? array_filter( $folder_types ) : [];
+
+ if ( ! $folder_types ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ foreach ( $folder_types as $folder_type_data ) {
+ $context = ! empty( $folder_type_data['context'] ) ? $folder_type_data['context'] : 'noop';
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
+ imagify_die();
+ }
+ }
+
+ wp_send_json_success( imagify_get_bulk_stats( array_flip( $folder_types ) ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WP OPTIMIZATION CALLBACKS =============================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Optimize all thumbnails of a specific image with the manual method.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Jonathan Buttigieg
+ */
+ public function imagify_manual_optimize_callback() {
+ $context = $this->get_context();
+ $media_id = $this->get_media_id();
+
+ if ( ! $media_id || ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ imagify_check_nonce( 'imagify-optimize-' . $media_id . '-' . $context );
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'manual-optimize', $media_id ) ) {
+ imagify_die();
+ }
+
+ $result = $this->optimize_media( $media_id, $context );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ $output = $result->get_error_message();
+
+ wp_send_json_error( [ 'html' => $output ] );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Optimize all thumbnails of a specific image with a different optimization level.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Jonathan Buttigieg
+ */
+ public function imagify_manual_reoptimize_callback() {
+ $context = $this->get_context();
+ $media_id = $this->get_media_id();
+
+ if ( ! $media_id || ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ imagify_check_nonce( 'imagify-manual-reoptimize-' . $media_id . '-' . $context );
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'manual-optimize', $media_id ) ) {
+ imagify_die();
+ }
+
+ $result = $this->reoptimize_media( $media_id, $context, $this->get_optimization_level() );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ $output = $result->get_error_message();
+
+ wp_send_json_error( [ 'html' => $output ] );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Optimize one or some thumbnails that are not optimized yet.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_optimize_missing_sizes_callback() {
+ $context = $this->get_context();
+ $media_id = $this->get_media_id();
+
+ if ( ! $media_id || ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ imagify_check_nonce( 'imagify-optimize-missing-sizes-' . $media_id . '-' . $context );
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'manual-optimize', $media_id ) ) {
+ imagify_die();
+ }
+
+ $result = $this->optimize_missing_sizes( $media_id, $context );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ $output = $result->get_error_message();
+
+ wp_send_json_error( [ 'html' => $output ] );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Generate webp images if they are missing.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_generate_webp_versions_callback() {
+ $context = $this->get_context();
+ $media_id = $this->get_media_id();
+
+ if ( ! $media_id || ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ imagify_check_nonce( 'imagify-generate-webp-versions-' . $media_id . '-' . $context );
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'manual-optimize', $media_id ) ) {
+ imagify_die();
+ }
+
+ $result = $this->generate_webp_versions( $media_id, $context );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ $output = $result->get_error_message();
+
+ wp_send_json_error( [ 'html' => $output ] );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Generate webp images if they are missing.
+ *
+ * @since 1.9.6
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_delete_webp_versions_callback() {
+ $context = $this->get_context();
+ $media_id = $this->get_media_id();
+
+ if ( ! $media_id || ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ imagify_check_nonce( 'imagify-delete-webp-versions-' . $media_id . '-' . $context );
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'manual-restore', $media_id ) ) {
+ imagify_die();
+ }
+
+ $result = $this->delete_webp_versions( $media_id, $context );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ $output = $result->get_error_message();
+
+ wp_send_json_error( [ 'html' => $output ] );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Process a restoration to the original attachment.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Jonathan Buttigieg
+ */
+ public function imagify_restore_callback() {
+ $context = $this->get_context();
+ $media_id = $this->get_media_id();
+
+ if ( ! $media_id || ! $context ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ imagify_check_nonce( 'imagify-restore-' . $media_id . '-' . $context );
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'manual-restore', $media_id ) ) {
+ imagify_die();
+ }
+
+ $result = $this->restore_media( $media_id, $context );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ $output = $result->get_error_message();
+
+ wp_send_json_error( [ 'html' => $output ] );
+ }
+
+ // Return the optimization button.
+ $output = Imagify_Views::get_instance()->get_template( 'button/optimize', [
+ 'url' => get_imagify_admin_url( 'optimize', array(
+ 'attachment_id' => $media_id,
+ 'context' => $context,
+ ) ),
+ ] );
+
+ wp_send_json_success( [ 'html' => $output ] );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** CUSTOM FOLDERS OPTIMIZATION CALLBACKS =================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Optimize a file.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_optimize_file_callback() {
+ imagify_check_nonce( 'imagify_optimize_file' );
+
+ $media_id = $this->get_media_id( 'GET', 'id' );
+
+ if ( ! $media_id ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manual-optimize', $media_id ) ) {
+ imagify_die();
+ }
+
+ $result = $this->optimize_media( $media_id, 'custom-folders' );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ wp_send_json_error( $result->get_error_message() );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Re-optimize a file.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_reoptimize_file_callback() {
+ imagify_check_nonce( 'imagify_reoptimize_file' );
+
+ $media_id = $this->get_media_id( 'GET', 'id' );
+
+ if ( ! $media_id ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manual-optimize', $media_id ) ) {
+ imagify_die();
+ }
+
+ $level = $this->get_optimization_level( 'GET', 'level' );
+
+ $result = $this->reoptimize_media( $media_id, 'custom-folders', $level );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ wp_send_json_error( $result->get_error_message() );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Restore a file.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_restore_file_callback() {
+ imagify_check_nonce( 'imagify_restore_file' );
+
+ $media_id = $this->get_media_id( 'GET', 'id' );
+
+ if ( ! $media_id ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manual-restore', $media_id ) ) {
+ imagify_die();
+ }
+
+ $result = $this->restore_media( $media_id, 'custom-folders' );
+
+ imagify_maybe_redirect( is_wp_error( $result ) ? $result : false );
+
+ if ( is_wp_error( $result ) ) {
+ // Return an error message.
+ wp_send_json_error( $result->get_error_message() );
+ }
+
+ $process = imagify_get_optimization_process( $media_id, 'custom-folders' );
+ $this->file_optimization_output( $process );
+ }
+
+ /**
+ * Check if a file has been modified, and update the database accordingly.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_refresh_file_modified_callback() {
+ imagify_check_nonce( 'imagify_refresh_file_modified' );
+
+ $media_id = $this->get_media_id( 'GET', 'id' );
+
+ if ( ! $media_id ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manual-optimize', $media_id ) ) {
+ imagify_die();
+ }
+
+ $process = imagify_get_optimization_process( $media_id, 'custom-folders' );
+ $result = Imagify_Custom_Folders::refresh_file( $process );
+
+ if ( is_wp_error( $result ) ) {
+ // The media is not valid or has been removed from the database.
+ $message = $result->get_error_message();
+
+ imagify_maybe_redirect( $message );
+
+ wp_send_json_error( array(
+ 'row' => $message,
+ ) );
+ }
+
+ imagify_maybe_redirect();
+
+ // Return some HTML to the ajax call.
+ $this->file_optimization_output( $process );
+ }
+
+ /**
+ * Look for new files in custom folders.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_scan_custom_folders_callback() {
+ imagify_check_nonce( 'imagify_scan_custom_folders' );
+
+ if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'optimize' ) ) {
+ imagify_die();
+ }
+
+ $folder = (int) filter_input( INPUT_GET, 'folder', FILTER_VALIDATE_INT );
+
+ if ( $folder > 0 ) {
+ // A specific custom folder (selected or not).
+ $folders_db = Imagify_Folders_DB::get_instance();
+ $folders_key = $folders_db->get_primary_key();
+ $folder = $folders_db->get( $folder );
+
+ if ( ! $folder ) {
+ // This should not happen.
+ imagify_maybe_redirect( __( 'This folder is not in the database.', 'imagify' ) );
+ }
+
+ $folder['folder_path'] = Imagify_Files_Scan::remove_placeholder( $folder['path'] );
+
+ $folders = array(
+ $folder[ $folders_key ] => $folder,
+ );
+
+ Imagify_Custom_Folders::get_files_from_folders( $folders, array(
+ 'add_inactive_folder_files' => true,
+ ) );
+
+ imagify_maybe_redirect();
+ }
+
+ // All selected custom folders.
+ $folders = Imagify_Custom_Folders::get_folders( array(
+ 'active' => true,
+ ) );
+ Imagify_Custom_Folders::get_files_from_folders( $folders );
+
+ imagify_maybe_redirect();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** SETTINGS PAGE CALLBACKS ================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Check if the backup directory is writable.
+ * This is used to display an error message in the plugin's settings page.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_check_backup_dir_is_writable_callback() {
+ imagify_check_nonce( 'imagify_check_backup_dir_is_writable' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ wp_send_json_success( array(
+ 'is_writable' => (int) Imagify_Requirements::attachments_backup_dir_is_writable(),
+ ) );
+ }
+
+ /**
+ * Get files and folders that are direct children of a given folder.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_get_files_tree_callback() {
+ imagify_check_nonce( 'get-files-tree' );
+
+ if ( ! imagify_get_context( 'custom-folders' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ if ( ! isset( $_POST['folder'] ) || '' === $_POST['folder'] ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ $folder = wp_unslash( $_POST['folder'] );
+ $folder = trailingslashit( sanitize_text_field( $folder ) );
+ $folder = realpath( $this->filesystem->get_site_root() . ltrim( $folder, '/' ) );
+
+ if ( ! $folder ) {
+ imagify_die( __( 'This folder doesn\'t exist.', 'imagify' ) );
+ }
+
+ if ( ! $this->filesystem->is_dir( $folder ) ) {
+ imagify_die( __( 'This file is not a folder.', 'imagify' ) );
+ }
+
+ $folder = $this->filesystem->normalize_dir_path( $folder );
+
+ if ( Imagify_Files_Scan::is_path_forbidden( $folder ) ) {
+ imagify_die( __( 'This folder is not allowed.', 'imagify' ) );
+ }
+
+ // Finally we made all our validations.
+ $selected = ! empty( $_POST['selected'] ) && is_array( $_POST['selected'] ) ? array_flip( wp_unslash( $_POST['selected'] ) ) : array();
+ $views = Imagify_Views::get_instance();
+ $output = '';
+
+ if ( $this->filesystem->is_site_root( $folder ) ) {
+ $output .= $views->get_template( 'part-settings-files-tree-row', array(
+ 'relative_path' => '/',
+ // Value #///# Label.
+ 'checkbox_value' => '{{ROOT}}/#///#' . esc_attr__( 'Site\'s root', 'imagify' ),
+ 'checkbox_id' => 'ABSPATH',
+ 'checkbox_selected' => isset( $selected['{{ROOT}}/'] ),
+ 'label' => __( 'Site\'s root', 'imagify' ),
+ 'no_button' => true,
+ ) );
+ }
+
+ $dir = new DirectoryIterator( $folder );
+ $dir = new Imagify_Files_Iterator( $dir );
+ $images = 0;
+
+ foreach ( new IteratorIterator( $dir ) as $file ) {
+ if ( ! $file->isDir() ) {
+ ++$images;
+ continue;
+ }
+
+ $folder_path = trailingslashit( $file->getPathname() );
+ $relative_path = $this->filesystem->make_path_relative( $folder_path );
+ $placeholder = Imagify_Files_Scan::add_placeholder( $folder_path );
+
+ $output .= $views->get_template( 'part-settings-files-tree-row', array(
+ 'relative_path' => esc_attr( $relative_path ),
+ // Value #///# Label.
+ 'checkbox_value' => esc_attr( $placeholder ) . '#///#' . esc_attr( $relative_path ),
+ 'checkbox_id' => sanitize_html_class( $placeholder ),
+ 'checkbox_selected' => isset( $selected[ $placeholder ] ),
+ 'label' => $this->filesystem->file_name( $folder_path ),
+ ) );
+ }
+
+ if ( $images ) {
+ /* translators: %s is a formatted number, dont use %d. */
+ $output .= ' ' . sprintf( _n( '%s Media File', '%s Media Files', $images, 'imagify' ), number_format_i18n( $images ) ) . ' ';
+ }
+
+ if ( ! $output ) {
+ $output .= '' . __( 'No optimizable files', 'imagify' ) . ' ';
+ }
+
+ wp_send_json_success( $output );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** IMAGIFY ACCOUNT CALLBACKS =============================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Create a new Imagify account.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Jonathan Buttigieg
+ */
+ public function imagify_signup_callback() {
+ imagify_check_nonce( 'imagify-signup', 'imagifysignupnonce' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ if ( empty( $_GET['email'] ) ) {
+ imagify_die( __( 'Empty email address.', 'imagify' ) );
+ }
+
+ $email = wp_unslash( $_GET['email'] );
+
+ if ( ! is_email( $email ) ) {
+ imagify_die( __( 'Not a valid email address.', 'imagify' ) );
+ }
+
+ $data = array(
+ 'email' => $email,
+ 'password' => wp_generate_password( 12, false ),
+ 'lang' => imagify_get_locale(),
+ );
+
+ $response = add_imagify_user( $data );
+
+ if ( is_wp_error( $response ) ) {
+ imagify_die( $response );
+ }
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Check the API key validity.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Jonathan Buttigieg
+ */
+ public function imagify_check_api_key_validity_callback() {
+ imagify_check_nonce( 'imagify-check-api-key', 'imagifycheckapikeynonce' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ if ( empty( $_GET['api_key'] ) ) {
+ imagify_die( __( 'Empty API key.', 'imagify' ) );
+ }
+
+ $api_key = wp_unslash( $_GET['api_key'] );
+ $response = get_imagify_status( $api_key );
+
+ if ( is_wp_error( $response ) ) {
+ imagify_die( $response );
+ }
+
+ update_imagify_option( 'api_key', $api_key );
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Get admin bar profile output.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Jonathan Buttigieg
+ */
+ public function imagify_get_admin_bar_profile_callback() {
+ imagify_check_nonce( 'imagify-get-admin-bar-profile', 'imagifygetadminbarprofilenonce' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ $user = new Imagify_User();
+ $views = Imagify_Views::get_instance();
+ $unconsumed_quota = $views->get_quota_percent();
+ $message = '';
+
+ if ( $unconsumed_quota <= 20 ) {
+ $message = '';
+ $message .= '
' . __( 'Oops, It\'s almost over!', 'imagify' ) . '
';
+ /* translators: %s is a line break. */
+ $message .= '
' . sprintf( __( 'You have almost used all your credit.%sDon\'t forget to upgrade your subscription to continue optimizing your images.', 'imagify' ), ' ' ) . '
';
+ $message .= '
' . __( 'View My Subscription', 'imagify' ) . '
';
+ $message .= '
';
+ }
+
+ if ( 0 === $unconsumed_quota ) {
+ $message = '';
+ $message .= '
' . __( 'Oops, It\'s Over!', 'imagify' ) . '
';
+ $message .= '
' . sprintf(
+ /* translators: 1 is a data quota, 2 is a date. */
+ __( 'You have consumed all your credit for this month. You will have %1$s back on %2$s .', 'imagify' ),
+ imagify_size_format( $user->quota * pow( 1024, 2 ) ),
+ date_i18n( get_option( 'date_format' ), strtotime( $user->next_date_update ) )
+ ) . '
';
+ $message .= '
' . __( 'Upgrade My Subscription', 'imagify' ) . '
';
+ $message .= '
';
+ }
+
+ // Custom HTML.
+ $quota_section = '';
+ $quota_section .= '
';
+
+ if ( 1 === $user->plan_id ) {
+ $quota_section .= '
' . $views->get_quota_icon() . '
';
+ }
+
+ $quota_section .= '
';
+ $quota_section .= '
' . __( 'Account status', 'imagify' ) . '
';
+ $quota_section .= '
' . __( 'Your subscription:', 'imagify' ) . ' ' . $user->plan_label . '
';
+ $quota_section .= '
'; // .imagify-account
+ $quota_section .= '
'; // .imagify-abq-row
+
+ if ( 1 === $user->plan_id ) {
+ $quota_section .= '
';
+ $quota_section .= '
';
+ /* translators: %s is a data quota. */
+ $quota_section .= '
' . sprintf( __( 'You have %s space credit left', 'imagify' ), '' . $unconsumed_quota . '% ' ) . '
';
+ $quota_section .= '
';
+ $quota_section .= '
';
+ $quota_section .= '
'; // .imagify-bar-{negative|neutral|positive}
+ $quota_section .= '
'; // .imagify-space-left
+ $quota_section .= '
'; // .imagify-abq-row
+ }
+
+ $quota_section .= '
';
+ $quota_section .= '';
+ $quota_section .= ' ';
+ $quota_section .= '' . __( 'View my subscription', 'imagify' ) . ' ';
+ $quota_section .= ' '; // .imagify-account-link
+ $quota_section .= '
'; // .imagify-abq-row
+ $quota_section .= '
'; // .imagify-admin-bar-quota
+ $quota_section .= $message;
+
+ wp_send_json_success( $quota_section );
+ }
+
+ /**
+ * Get pricings from API for Onetime and Plans at the same time.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Geoffrey Crofte
+ */
+ public function imagify_get_prices_callback() {
+ imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ $prices_all = get_imagify_all_prices();
+
+ if ( is_wp_error( $prices_all ) ) {
+ imagify_die( $prices_all );
+ }
+
+ if ( ! is_object( $prices_all ) ) {
+ imagify_die( __( 'Wrongly formatted response from our server.', 'imagify' ) );
+ }
+
+ wp_send_json_success( array(
+ 'onetimes' => $prices_all->Packs,
+ 'monthlies' => $prices_all->Plans,
+ ) );
+ }
+
+ /**
+ * Check Coupon code on modal popin.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Geoffrey Crofte
+ */
+ public function imagify_check_coupon_callback() {
+ imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ if ( empty( $_POST['coupon'] ) ) {
+ wp_send_json_success( array(
+ 'success' => false,
+ 'detail' => __( 'Coupon is empty.', 'imagify' ),
+ ) );
+ }
+
+ $coupon = wp_unslash( $_POST['coupon'] );
+ $coupon = check_imagify_coupon_code( $coupon );
+
+ if ( is_wp_error( $coupon ) ) {
+ imagify_die( $coupon );
+ }
+
+ wp_send_json_success( imagify_translate_api_message( $coupon ) );
+ }
+
+ /**
+ * Get current discount promotion to display information on payment modal.
+ *
+ * @since 1.6.11
+ * @author Geoffrey Crofte
+ */
+ public function imagify_get_discount_callback() {
+ imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ wp_send_json_success( imagify_translate_api_message( check_imagify_discount() ) );
+ }
+
+ /**
+ * Get estimated sizes from the WordPress library.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Geoffrey Crofte
+ */
+ public function imagify_get_images_counts_callback() {
+ imagify_check_nonce( 'imagify_get_pricing_' . get_current_user_id(), 'imagifynonce' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ $raw_total_size_in_library = imagify_calculate_total_size_images_library() + Imagify_Files_Stats::get_overall_original_size();
+ $raw_average_per_month = imagify_calculate_average_size_images_per_month() + Imagify_Files_Stats::calculate_average_size_per_month();
+
+ Imagify_Data::get_instance()->set( array(
+ 'total_size_images_library' => $raw_total_size_in_library,
+ 'average_size_images_per_month' => $raw_average_per_month,
+ ) );
+
+ wp_send_json_success( array(
+ 'total_library_size' => array(
+ 'raw' => $raw_total_size_in_library,
+ 'human' => imagify_size_format( $raw_total_size_in_library ),
+ ),
+ 'average_month_size' => array(
+ 'raw' => $raw_average_per_month,
+ 'human' => imagify_size_format( $raw_average_per_month ),
+ ),
+ ) );
+ }
+
+ /**
+ * Estimate sizes and update the options values for them.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Remy Perona
+ */
+ public function imagify_update_estimate_sizes_callback() {
+ imagify_check_nonce( 'update_estimate_sizes' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ $raw_total_size_in_library = imagify_calculate_total_size_images_library() + Imagify_Files_Stats::get_overall_original_size();
+ $raw_average_per_month = imagify_calculate_average_size_images_per_month() + Imagify_Files_Stats::calculate_average_size_per_month();
+
+ Imagify_Data::get_instance()->set( array(
+ 'total_size_images_library' => $raw_total_size_in_library,
+ 'average_size_images_per_month' => $raw_average_per_month,
+ ) );
+
+ die( 1 );
+ }
+
+ /**
+ * Get the Imagify User data.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_get_user_data_callback() {
+ imagify_check_nonce( 'imagify_get_user_data' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ $user = imagify_cache_user();
+
+ if ( ! $user || ! $user->id ) {
+ imagify_die( __( 'Couldn\'t get user data.', 'imagify' ) );
+ }
+
+ // Remove useless sensitive data.
+ unset( $user->email );
+
+ if ( ! $user->get_percent_unconsumed_quota ) {
+ $user->best_plan_title = __( 'Oops, It\'s Over!', 'imagify' );
+ } elseif ( $user->get_percent_unconsumed_quota <= 20 ) {
+ $user->best_plan_title = __( 'Oops, It\'s almost over!', 'imagify' );
+ } else {
+ $user->best_plan_title = __( 'You\'re new to Imagify?', 'imagify' );
+ }
+
+ wp_send_json_success( $user );
+ }
+
+ /**
+ * Delete the Imagify User data cache.
+ *
+ * @since 1.9.5
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_delete_user_data_cache_callback() {
+ imagify_check_nonce( 'imagify_delete_user_data_cache' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ imagify_delete_cached_user();
+
+ wp_send_json_success();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** VARIOUS CALLBACKS ======================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Bridge between XML-RPC and actions triggered by imagify_do_async_job().
+ * When XML-RPC is used, a current user is set, but no cookies are set, so they cannot be sent with the request. Instead we stored the user ID in a transient.
+ *
+ * @since 1.6.11
+ * @access public
+ * @author Grégory Viguier
+ * @see imagify_do_async_job()
+ */
+ public function nopriv_imagify_rpc_callback() {
+ if ( empty( $_POST['imagify_rpc_action'] ) || empty( $_POST['imagify_rpc_id'] ) ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ $action = wp_unslash( $_POST['imagify_rpc_action'] ); // WPCS: CSRF ok.
+
+ if ( 32 !== strlen( $action ) ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ // Not necessary but just in case, whitelist the original action.
+ $actions = array_flip( $this->ajax_only_actions );
+ unset( $actions['nopriv_imagify_rpc'] );
+
+ if ( ! isset( $actions[ $action ] ) ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ // Get the user ID.
+ $rpc_id = sanitize_key( $_POST['imagify_rpc_id'] );
+ $user_id = absint( get_transient( 'imagify_rpc_' . $rpc_id ) );
+ $user = $user_id ? get_userdata( $user_id ) : false;
+
+ delete_transient( 'imagify_rpc_' . $rpc_id );
+
+ if ( ! $user || ! $user->exists() ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ // The current user must be set before verifying the nonce.
+ wp_set_current_user( $user_id );
+
+ imagify_check_nonce( 'imagify_rpc_' . $rpc_id, 'imagify_rpc_nonce' );
+
+ // Trigger the action we originally wanted.
+ $_POST['action'] = $action;
+ unset( $_POST['imagify_rpc_action'], $_POST['imagify_rpc_id'], $_POST['imagify_rpc_nonce'] );
+
+ /** This hook is documented in wp-admin/admin-ajax.php. */
+ do_action( 'wp_ajax_' . $action );
+ }
+
+ /**
+ * Store the "closed" status of the ads.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function imagify_dismiss_ad_callback() {
+ imagify_check_nonce( 'imagify-dismiss-ad' );
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'manage' ) ) {
+ imagify_die();
+ }
+
+ $notice = filter_input( INPUT_GET, 'ad', FILTER_SANITIZE_STRING );
+
+ if ( ! $notice ) {
+ imagify_maybe_redirect();
+ wp_send_json_error();
+ }
+
+ $user_id = get_current_user_id();
+ $notices = get_user_meta( $user_id, '_imagify_ignore_ads', true );
+ $notices = $notices && is_array( $notices ) ? array_flip( $notices ) : array();
+
+ if ( isset( $notices[ $notice ] ) ) {
+ imagify_maybe_redirect();
+ wp_send_json_success();
+ }
+
+ $notices = array_flip( $notices );
+ $notices[] = $notice;
+ $notices = array_filter( $notices );
+ $notices = array_values( $notices );
+
+ update_user_meta( $user_id, '_imagify_ignore_ads', $notices );
+
+ imagify_maybe_redirect();
+ wp_send_json_success();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** VARIOUS HELPERS ========================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the submitted optimization level.
+ *
+ * @since 1.7
+ * @since 1.9 Added $method and $parameter parameters.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $method The method used: 'GET' (default), or 'POST'.
+ * @param string $parameter The name of the parameter to look for.
+ * @return int
+ */
+ public function get_optimization_level( $method = 'GET', $parameter = 'optimization_level' ) {
+ $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
+ $level = filter_input( $method, $parameter );
+
+ if ( ! is_numeric( $level ) || $level < 0 || $level > 2 ) {
+ return get_imagify_option( 'optimization_level' );
+ }
+
+ return (int) $level;
+ }
+
+ /**
+ * Get the submitted context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $method The method used: 'GET' (default), or 'POST'.
+ * @param string $parameter The name of the parameter to look for.
+ * @return string
+ */
+ public function get_context( $method = 'GET', $parameter = 'context' ) {
+ $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
+ $context = filter_input( $method, $parameter, FILTER_SANITIZE_STRING );
+
+ return imagify_sanitize_context( $context );
+ }
+
+ /**
+ * Get the submitted media ID.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $method The method used: 'GET' (default), or 'POST'.
+ * @param string $parameter The name of the parameter to look for.
+ * @return int
+ */
+ public function get_media_id( $method = 'GET', $parameter = 'attachment_id' ) {
+ $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
+ $media_id = filter_input( $method, $parameter );
+
+ if ( ! is_numeric( $media_id ) || $media_id < 0 ) {
+ return 0;
+ }
+
+ return (int) $media_id;
+ }
+
+ /**
+ * Get the submitted folder_type.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $method The method used: 'GET' (default), or 'POST'.
+ * @param string $parameter The name of the parameter to look for.
+ * @return string
+ */
+ public function get_folder_type( $method = 'GET', $parameter = 'folder_type' ) {
+ $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
+
+ return filter_input( $method, $parameter, FILTER_SANITIZE_STRING );
+ }
+
+ /**
+ * Get the submitted imagify action.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $method The method used: 'GET' (default), or 'POST'.
+ * @param string $parameter The name of the parameter to look for.
+ * @return string
+ */
+ public function get_imagify_action( $method = 'GET', $parameter = 'imagify_action' ) {
+ $method = 'POST' === $method ? INPUT_POST : INPUT_GET;
+ $action = filter_input( $method, $parameter, FILTER_SANITIZE_STRING );
+
+ return $action ? $action : 'optimize';
+ }
+
+ /**
+ * Get the Bulk class name depending on a context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
+ * @return string The Bulk class name.
+ */
+ public function get_bulk_class_name( $context ) {
+ switch ( $context ) {
+ case 'wp':
+ $class_name = '\\Imagify\\Bulk\\WP';
+ break;
+
+ case 'custom-folders':
+ $class_name = '\\Imagify\\Bulk\\CustomFolders';
+ break;
+
+ default:
+ $class_name = '\\Imagify\\Bulk\\Noop';
+ }
+
+ /**
+ * Filter the name of the class to use for bulk process.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param int $class_name The class name.
+ * @param string $context The context name.
+ */
+ $class_name = apply_filters( 'imagify_bulk_class_name', $class_name, $context );
+
+ return '\\' . ltrim( $class_name, '\\' );
+ }
+
+ /**
+ * Get the Bulk instance depending on a context.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $context The context name. Default values are 'wp' and 'custom-folders'.
+ * @return BulkInterface The optimization process instance.
+ */
+ public function get_bulk_instance( $context ) {
+ $class_name = $this->get_bulk_class_name( $context );
+ return new $class_name();
+ }
+
+ /**
+ * Check if the user has a valid account and has quota. Die on failure.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function check_can_optimize() {
+ if ( ! Imagify_Requirements::is_api_key_valid() ) {
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+ wp_send_json_error( array( 'message' => 'invalid-api-key' ) );
+ }
+
+ imagify_die( __( 'Your API key is not valid!', 'imagify' ) );
+ }
+
+ if ( Imagify_Requirements::is_over_quota() ) {
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+ wp_send_json_error( array( 'message' => 'over-quota' ) );
+ }
+
+ imagify_die( __( 'You have used all your credits!', 'imagify' ) );
+ }
+ }
+
+ /**
+ * Get a media columns for the "Other Media" page.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param object $process A \Imagify\Optimization\Process\CustomFolders object.
+ * @param object $list_table A Imagify_Files_List_Table object.
+ * @return array An array of HTML, keyed by column name.
+ */
+ public function get_media_columns( $process, $list_table ) {
+ $item = (object) [ 'process' => $process ];
+
+ return [
+ 'folder' => $list_table->get_column( 'folder', $item ),
+ 'optimization' => $list_table->get_column( 'optimization', $item ),
+ 'status' => $list_table->get_column( 'status', $item ),
+ 'optimization_level' => $list_table->get_column( 'optimization_level', $item ),
+ 'actions' => $list_table->get_column( 'actions', $item ),
+ 'title' => $list_table->get_column( 'title', $item ), // This one must remain after the "optimization" column, otherwize the data for the comparison tool won't be up-to-date.
+ ];
+ }
+
+ /**
+ * After a file optimization, restore, or whatever, redirect the user or output HTML for ajax.
+ *
+ * @since 1.7
+ * @since 1.9 Removed parameter $result.
+ * @since 1.9 Added $folder in the returned JSON.
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $process A \Imagify\Optimization\Process\CustomFolders object.
+ */
+ protected function file_optimization_output( $process ) {
+ $list_table = new Imagify_Files_List_Table( [
+ 'screen' => 'imagify-files',
+ ] );
+
+ wp_send_json_success( [
+ 'columns' => $this->get_media_columns( $process, $list_table ),
+ ] );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-assets.php b/wp-content/plugins/imagify/inc/classes/class-imagify-assets.php
new file mode 100644
index 00000000..b0909d48
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-assets.php
@@ -0,0 +1,786 @@
+is_admin_bar_item_showing() ) {
+ return;
+ }
+
+ $this->register_style( 'admin-bar' );
+ $this->register_script( 'admin-bar', 'admin-bar', array( 'jquery' ) );
+
+ $this->enqueue_assets( 'admin-bar' )->localize( 'imagifyAdminBar' );
+ }
+
+ /**
+ * Register stylesheets and scripts for the administration area.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ */
+ public function register_styles_and_scripts() {
+ static $done = false;
+
+ if ( $done ) {
+ return;
+ }
+ $done = true;
+
+ /**
+ * 3rd Party Styles.
+ */
+ $this->register_style( 'sweetalert-core', 'sweetalert2', array(), '4.6.6' );
+
+ /**
+ * Imagify Styles.
+ */
+ $this->register_style( 'sweetalert', 'sweetalert-custom', array( 'sweetalert-core' ) );
+
+ $this->register_style( 'admin-bar' );
+
+ $this->register_style( 'admin' );
+
+ $this->register_style( 'notices', 'notices', array( 'admin' ) ); // Needs SweetAlert on some cases.
+
+ $this->register_style( 'twentytwenty', 'twentytwenty', array( 'admin' ) );
+
+ $this->register_style( 'pricing-modal', 'pricing-modal', array( 'admin' ) );
+
+ $this->register_style( 'bulk', 'bulk', array( 'sweetalert', 'admin' ) );
+
+ $this->register_style( 'options', 'options', array( 'sweetalert', 'admin' ) );
+
+ $this->register_style( 'files-list', 'files-list', array( 'admin' ) );
+
+ /**
+ * 3rd Party Scripts.
+ */
+ $this->register_script( 'promise-polyfill', 'es6-promise.auto', array(), '4.1.1' );
+
+ $this->register_script( 'sweetalert', 'sweetalert2', array( 'promise-polyfill' ), '4.6.6' )->localize( 'imagifySwal' );
+
+ $this->register_script( 'chart', 'chart', array(), '2.7.1.0' );
+
+ $this->register_script( 'event-move', 'jquery.event.move', array( 'jquery' ), '2.0.1' );
+
+ /**
+ * Imagify Scripts.
+ */
+ $this->register_script( 'admin-bar', 'admin-bar', array( 'jquery' ) )->defer_localization( 'imagifyAdminBar' );
+
+ $this->register_script( 'admin', 'admin', array( 'jquery' ) );
+
+ $this->register_script( 'notices', 'notices', array( 'jquery', 'admin' ) )->defer_localization( 'imagifyNotices' ); // Needs SweetAlert on some cases.
+
+ $this->register_script( 'twentytwenty', 'jquery.twentytwenty', array( 'jquery', 'event-move', 'chart', 'admin' ) )->defer_localization( 'imagifyTTT' );
+
+ $this->register_script( 'beat', 'beat', array( 'jquery' ) )->localize( 'imagifybeatSettings' );
+
+ $this->register_script( 'media-modal', 'media-modal', array( 'jquery', 'beat', 'underscore', 'chart', 'admin' ) )->localize( 'imagifyModal' );
+
+ $this->register_script( 'pricing-modal', 'pricing-modal', array( 'jquery', 'admin' ) )->defer_localization( 'imagifyPricingModal' );
+
+ $this->register_script( 'library', 'library', array( 'jquery', 'media-modal' ) )->defer_localization( 'imagifyLibrary' );
+
+ $this->register_script( 'async', 'imagify-gulp' );
+
+ $this->register_script( 'bulk', 'bulk', array( 'jquery', 'beat', 'underscore', 'chart', 'sweetalert', 'async', 'admin' ) )->defer_localization( 'imagifyBulk' );
+
+ $this->register_script( 'options', 'options', array( 'jquery', 'beat', 'sweetalert', 'underscore', 'admin' ) )->defer_localization( 'imagifyOptions' );
+
+ $this->register_script( 'files-list', 'files-list', array( 'jquery', 'beat', 'underscore', 'chart', 'admin' ) )->defer_localization( 'imagifyFiles' );
+ }
+
+ /**
+ * Enqueue stylesheets and scripts for the administration area.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ */
+ public function enqueue_styles_and_scripts() {
+ static $done = false;
+
+ if ( $done ) {
+ return;
+ }
+ $done = true;
+
+ /*
+ * Register stylesheets and scripts.
+ */
+ $this->register_styles_and_scripts();
+
+ /**
+ * Admin bar.
+ */
+ if ( $this->is_admin_bar_item_showing() ) {
+ $this->enqueue_assets( 'admin-bar' );
+ }
+
+ /**
+ * Notices.
+ */
+ $notices = Imagify_Notices::get_instance();
+
+ if ( $notices->has_notices() ) {
+ if ( $notices->display_welcome_steps() || $notices->display_wrong_api_key() ) {
+ // This is where we display things about the API key.
+ $this->enqueue_assets( 'sweetalert' );
+ }
+
+ $this->enqueue_assets( 'notices' );
+ }
+
+ /**
+ * Loaded in the library and attachment edition.
+ */
+ if ( imagify_is_screen( 'library' ) || imagify_is_screen( 'attachment' ) ) {
+ $this->enqueue_assets( 'twentytwenty' );
+ }
+
+ /**
+ * Loaded in the library.
+ */
+ if ( imagify_is_screen( 'library' ) ) {
+ $this->enqueue_style( 'admin' )->enqueue_script( 'library' );
+ }
+
+ /**
+ * Loaded in the bulk optimization page.
+ */
+ if ( imagify_is_screen( 'bulk' ) ) {
+ $this->enqueue_assets( array( 'pricing-modal', 'bulk' ) );
+ }
+
+ /*
+ * Loaded in the settings page.
+ */
+ if ( imagify_is_screen( 'imagify-settings' ) ) {
+ $this->enqueue_assets( array( 'sweetalert', 'notices', 'twentytwenty', 'pricing-modal', 'options' ) );
+ }
+
+ /*
+ * Loaded in the files list page.
+ */
+ if ( imagify_is_screen( 'files-list' ) ) {
+ $this->enqueue_assets( array( 'files-list', 'twentytwenty' ) );
+ }
+
+ /**
+ * Triggered after Imagify CSS and JS have been enqueued.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ */
+ do_action( 'imagify_assets_enqueued' );
+ }
+
+ /**
+ * Enqueue stylesheets and scripts for the media modal.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ */
+ public function enqueue_media_modal() {
+ static $done = false;
+
+ if ( $done ) {
+ return;
+ }
+ $done = true;
+
+ /*
+ * Register stylesheets and scripts.
+ */
+ $this->register_styles_and_scripts();
+
+ $this->enqueue_style( 'admin' )->enqueue_script( 'media-modal' );
+
+ // When the optimization buttons are displayed in the media modal, they are fetched through ajax, so they canât print the "processing" button template in the footer.
+ Imagify_Views::get_instance()->print_js_template_in_footer( 'button/processing' );
+
+ /**
+ * Triggered after Imagify CSS and JS have been enqueued for the media modal.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ */
+ do_action( 'imagify_media_modal_assets_enqueued' );
+ }
+
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PUBLIC TOOLS ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Register a style.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $handle Name of the stylesheet. Should be unique.
+ * @param string|null $file_name The file name, without the extension. If null, $handle is used.
+ * @param array $dependencies An array of registered stylesheet handles this stylesheet depends on.
+ * @param string|null $version String specifying stylesheet version number. If set to null, the plugin version is used. If SCRIPT_DEBUG is true, a random string is used.
+ * @return object This class instance.
+ */
+ public function register_style( $handle, $file_name = null, $dependencies = array(), $version = null ) {
+ // If we register it, it's one of our styles.
+ $this->styles[ $handle ] = 1;
+ $this->current_handle = $handle;
+ $this->current_handle_type = 'css';
+
+ $file_name = $file_name ? $file_name : $handle;
+ $version = $version ? $version : IMAGIFY_VERSION;
+ $version = $this->is_debug() ? self::$version : $version;
+ $extension = $this->is_debug() ? '.css' : '.min.css';
+ $handle = self::CSS_PREFIX . $handle;
+ $dependencies = $this->prefix_dependencies( $dependencies, 'css' );
+
+ wp_register_style(
+ $handle,
+ IMAGIFY_URL . 'assets/css/' . $file_name . $extension,
+ $dependencies,
+ $version
+ );
+
+ return $this;
+ }
+
+ /**
+ * Enqueue a style.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string|array $handles Name of the stylesheet. Should be unique. Can be an array to enqueue several stylesheets.
+ * @return object This class instance.
+ */
+ public function enqueue_style( $handles ) {
+ $handles = (array) $handles;
+
+ foreach ( $handles as $handle ) {
+ $this->current_handle = $handle;
+ $this->current_handle_type = 'css';
+
+ if ( ! empty( $this->styles[ $handle ] ) ) {
+ // If we registered it, it's one of our styles.
+ $handle = self::CSS_PREFIX . $handle;
+ }
+
+ wp_enqueue_style( $handle );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Dequeue a style.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string|array $handles Name of the stylesheet. Should be unique. Can be an array to dequeue several stylesheets.
+ * @return object This class instance.
+ */
+ public function dequeue_style( $handles ) {
+ $handles = (array) $handles;
+
+ foreach ( $handles as $handle ) {
+ $this->current_handle = $handle;
+ $this->current_handle_type = 'css';
+
+ if ( ! empty( $this->styles[ $handle ] ) ) {
+ // If we registered it, it's one of our styles.
+ $handle = self::CSS_PREFIX . $handle;
+ }
+
+ wp_dequeue_style( $handle );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Register a script.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $handle Name of the script. Should be unique.
+ * @param string|null $file_name The file name, without the extension. If null, $handle is used.
+ * @param array $dependencies An array of registered script handles this script depends on.
+ * @param string|null $version String specifying script version number. If set to null, the plugin version is used. If SCRIPT_DEBUG is true, a random string is used.
+ * @return object This class instance.
+ */
+ public function register_script( $handle, $file_name = null, $dependencies = array(), $version = null ) {
+ // If we register it, it's one of our scripts.
+ $this->scripts[ $handle ] = 1;
+ // Set the current handler and handler type.
+ $this->current_handle = $handle;
+ $this->current_handle_type = 'js';
+
+ $file_name = $file_name ? $file_name : $handle;
+ $version = $version ? $version : IMAGIFY_VERSION;
+ $version = $this->is_debug() ? self::$version : $version;
+ $extension = $this->is_debug() ? '.js' : '.min.js';
+ $handle = self::JS_PREFIX . $handle;
+ $dependencies = $this->prefix_dependencies( $dependencies );
+
+ wp_register_script(
+ $handle,
+ IMAGIFY_URL . 'assets/js/' . $file_name . $extension,
+ $dependencies,
+ $version,
+ true
+ );
+
+ return $this;
+ }
+
+ /**
+ * Enqueue a script.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string|array $handles Name of the script. Should be unique. Can be an array to enqueue several scripts.
+ * @return object This class instance.
+ */
+ public function enqueue_script( $handles ) {
+ $handles = (array) $handles;
+
+ foreach ( $handles as $handle ) {
+ // Enqueue the corresponding style.
+ if ( ! empty( $this->styles[ $handle ] ) ) {
+ $this->enqueue_style( $handle );
+ }
+
+ $this->current_handle = $handle;
+ $this->current_handle_type = 'js';
+
+ if ( ! empty( $this->scripts[ $handle ] ) ) {
+ // If we registered it, it's one of our scripts.
+ $handle = self::JS_PREFIX . $handle;
+ }
+
+ wp_enqueue_script( $handle );
+
+ // Deferred localization.
+ if ( ! empty( $this->deferred_localizations[ $this->current_handle ] ) ) {
+ array_map( array( $this, 'localize' ), $this->deferred_localizations[ $this->current_handle ] );
+ unset( $this->deferred_localizations[ $this->current_handle ] );
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Dequeue a script.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string|array $handles Name of the script. Should be unique. Can be an array to dequeue several scripts.
+ * @return object This class instance.
+ */
+ public function dequeue_script( $handles ) {
+ $handles = (array) $handles;
+
+ foreach ( $handles as $handle ) {
+ // Enqueue the corresponding style.
+ if ( ! empty( $this->styles[ $handle ] ) ) {
+ $this->dequeue_style( $handle );
+ }
+
+ $this->current_handle = $handle;
+ $this->current_handle_type = 'js';
+
+ if ( ! empty( $this->scripts[ $handle ] ) ) {
+ // If we registered it, it's one of our scripts.
+ $handle = self::JS_PREFIX . $handle;
+ }
+
+ wp_dequeue_script( $handle );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Localize a script.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $handle Name of the script. Should be unique.
+ * @param string $object_name Name for the JavaScript object. Passed directly, so it should be qualified JS variable. Example: '/[a-zA-Z0-9_]+/'.
+ * @param string|array|null $l10n The data itself. The data can be either a single or multi-dimensional array. If null, $handle is used.
+ * @return object This class instance.
+ */
+ public function localize_script( $handle, $object_name, $l10n = null ) {
+ $this->current_handle = $handle;
+ $this->current_handle_type = 'js';
+
+ if ( ! isset( $l10n ) ) {
+ $l10n = $handle;
+ }
+
+ if ( is_string( $l10n ) ) {
+ $l10n = $this->get_localization_data( $l10n );
+ }
+
+ if ( ! $l10n ) {
+ return $this;
+ }
+
+ if ( ! empty( $this->scripts[ $handle ] ) ) {
+ // If we registered it, it's one of our scripts.
+ $handle = self::JS_PREFIX . $handle;
+ }
+
+ wp_localize_script( $handle, $object_name, $l10n );
+
+ return $this;
+ }
+
+ /**
+ * Enqueue a style and a script that have the same handle.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string|array $handles Name of the script. Should be unique. Can be an array to enqueue several scripts.
+ * @return object This class instance.
+ */
+ public function enqueue_assets( $handles ) {
+ $handles = (array) $handles;
+
+ foreach ( $handles as $handle ) {
+ $this->enqueue_script( $handle );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Dequeue a style and a script that have the same handle.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string|array $handles Name of the script. Should be unique. Can be an array to dequeue several scripts.
+ * @return object This class instance.
+ */
+ public function dequeue_assets( $handles ) {
+ $handles = (array) $handles;
+
+ foreach ( $handles as $handle ) {
+ $this->dequeue_style( $handle );
+ $this->dequeue_script( $handle );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Enqueue the current script or style.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return object This class instance.
+ */
+ public function enqueue() {
+ if ( 'js' === $this->current_handle_type ) {
+ $this->enqueue_script( $this->current_handle );
+ } elseif ( 'css' === $this->current_handle_type ) {
+ $this->enqueue_style( $this->current_handle );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Localize the current script.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $object_name Name for the JavaScript object. Passed directly, so it should be qualified JS variable. Example: '/[a-zA-Z0-9_]+/'.
+ * @param string|array|null $l10n The data itself. The data can be either a single or multi-dimensional array. If null, $handle is used.
+ * @return object This class instance.
+ */
+ public function localize( $object_name, $l10n = null ) {
+ return $this->localize_script( $this->current_handle, $object_name, $l10n );
+ }
+
+ /**
+ * Localize the current script when it is enqueued with `$this->enqueue()` or `$this->enqueue_script()`. This should be used right after `$this->register_script()`.
+ * Be careful, it won't work if the script is enqueued because it's a dependency.
+ * This is handy to not forget to localize the script later. It also prevents to localize the script right away, and maybe execute all localizations while the script is not enqueued (so we localize for nothing).
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $object_name Name for the JavaScript object. Passed directly, so it should be qualified JS variable. Example: '/[a-zA-Z0-9_]+/'.
+ * @return object This class instance.
+ */
+ public function defer_localization( $object_name ) {
+ if ( ! isset( $this->deferred_localizations[ $this->current_handle ] ) ) {
+ $this->deferred_localizations[ $this->current_handle ] = array();
+ }
+
+ $this->deferred_localizations[ $this->current_handle ][ $object_name ] = $object_name;
+
+ return $this;
+ }
+
+ /**
+ * Remove a deferred localization.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $handle Name of the script. Should be unique.
+ * @param string $object_name Name for the JavaScript object. Passed directly, so it should be qualified JS variable. Example: '/[a-zA-Z0-9_]+/'.
+ * @return object This class instance.
+ */
+ public function remove_deferred_localization( $handle, $object_name = null ) {
+ if ( empty( $this->deferred_localizations[ $handle ] ) ) {
+ return $this;
+ }
+
+ if ( $object_name ) {
+ unset( $this->deferred_localizations[ $handle ][ $object_name ] );
+ } else {
+ unset( $this->deferred_localizations[ $handle ] );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get all translations we can use with wp_localize_script().
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $context The translation context.
+ * @param array $more_data More data to merge.
+ * @return array $translations The translations.
+ */
+ public function get_localization_data( $context, $more_data = array() ) {
+ $data = get_imagify_localize_script_translations( $context );
+
+ if ( $more_data ) {
+ return array_merge( $data, $more_data );
+ }
+
+ return $data;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** INTERNAL TOOLS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Prefix the dependencies if they are ours.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param array $dependencies An array of registered script handles this script depends on.
+ * @param string $type Type of dependency: css or js.
+ * @return array
+ */
+ protected function prefix_dependencies( $dependencies, $type = 'js' ) {
+ if ( ! $dependencies ) {
+ return array();
+ }
+
+ if ( 'js' === $type ) {
+ $prefix = self::JS_PREFIX;
+ $scripts = $this->scripts;
+ } else {
+ $prefix = self::CSS_PREFIX;
+ $scripts = $this->styles;
+ }
+
+ $depts = array();
+
+ foreach ( $dependencies as $dept ) {
+ if ( ! empty( $scripts[ $dept ] ) ) {
+ $depts[] = $prefix . $dept;
+ } else {
+ $depts[] = $dept;
+ }
+ }
+
+ return $depts;
+ }
+
+ /**
+ * Tell if debug is on.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ protected function is_debug() {
+ return defined( 'IMAGIFY_DEBUG' ) && IMAGIFY_DEBUG || defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
+ }
+
+ /**
+ * Tell if the admin bar item is displaying.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ protected function is_admin_bar_item_showing() {
+ if ( defined( 'IMAGIFY_HIDDEN_ACCOUNT' ) && IMAGIFY_HIDDEN_ACCOUNT ) {
+ return false;
+ }
+
+ return get_imagify_option( 'api_key' ) && is_admin_bar_showing() && imagify_get_context( 'wp' )->current_user_can( 'manage' ) && get_imagify_option( 'admin_bar_menu' );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-auto-optimization.php b/wp-content/plugins/imagify/inc/classes/class-imagify-auto-optimization.php
new file mode 100644
index 00000000..0d4cf1a3
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-auto-optimization.php
@@ -0,0 +1,652 @@
+is_wp_53 = version_compare( $wp_version, '5.3-alpha1' ) >= 0;
+
+ // Automatic optimization tunel.
+ add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
+ add_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority, 2 );
+ add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 );
+
+ if ( $this->is_wp_53 ) {
+ // WP 5.3+.
+ add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority, 2 );
+ // Upload failure recovering.
+ add_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); // Before WPâs hook (priority 1).
+ } else {
+ add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
+ add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
+ }
+
+ add_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority, 3 );
+
+ // Prevent to re-optimize when updating the image width and height (when resizing the full image).
+ add_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 );
+ add_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 );
+ }
+
+ /**
+ * Remove the hooks.
+ *
+ * @since 1.8.4
+ */
+ public function remove_hooks() {
+ $priority = IMAGIFY_INT_MAX - 30;
+
+ // Automatic optimization tunel.
+ remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
+ remove_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority );
+ remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority );
+
+ if ( $this->is_wp_53 ) {
+ // WP 5.3+.
+ remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority );
+ // Upload failure recovering.
+ remove_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 );
+ } else {
+ remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
+ remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
+ }
+
+ remove_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority );
+
+ // Prevent to re-optimize when updating the image width and height (when resizing the full image).
+ remove_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 );
+ remove_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** HOOKS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Store the "upload step" when an attachment has just been uploaded.
+ *
+ * @since 1.8.4
+ * @see $this->store_ids_to_optimize()
+ *
+ * @param int $attachment_id Current attachment ID.
+ */
+ public function store_upload_ids( $attachment_id ) {
+ if ( ! self::is_optimization_prevented( $attachment_id ) && imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
+ $this->set_step( $attachment_id, 'upload' );
+ }
+ }
+
+ /**
+ * Store the "generate step" when wp_generate_attachment_metadata() is used.
+ *
+ * @since 1.9.10
+ *
+ * @param array $metadata An array of attachment meta data.
+ * @param int $attachment_id Current attachment ID.
+ * @return array
+ */
+ public function maybe_store_generate_step( $metadata, $attachment_id ) {
+ if ( self::is_optimization_prevented( $attachment_id ) ) {
+ return $metadata;
+ }
+
+ if ( empty( $metadata ) || ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
+ $this->unset_steps( $attachment_id );
+ return $metadata;
+ }
+
+ $this->set_step( $attachment_id, 'generate' );
+
+ return $metadata;
+ }
+
+ /**
+ * After the attachment meta data has been generated (partially, since WP 5.3), init the auto-optimization.
+ * Two cases are possible to trigger the optimization:
+ * - It's a new upload and auto-optimization is enabled.
+ * - It's not a new upload (it is regenerated) and the attachment is already optimized.
+ *
+ * @since 1.8.4
+ *
+ * @param array $metadata An array of attachment meta data.
+ * @param int $attachment_id Current attachment ID.
+ * @return array
+ */
+ public function store_ids_to_optimize( $metadata, $attachment_id ) {
+ static $auto_optimize;
+
+ if ( self::is_optimization_prevented( $attachment_id ) ) {
+ return $metadata;
+ }
+
+ if ( empty( $metadata ) || ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
+ $this->unset_steps( $attachment_id );
+ return $metadata;
+ }
+
+ if ( ! $this->has_step( $attachment_id, 'generate' ) ) {
+ return $metadata;
+ }
+
+ $is_new_upload = $this->has_step( $attachment_id, 'upload' );
+
+ if ( $is_new_upload ) {
+ // It's a new upload.
+ if ( ! isset( $auto_optimize ) ) {
+ $auto_optimize = get_imagify_option( 'auto_optimize' );
+ }
+
+ if ( ! $auto_optimize ) {
+ /**
+ * Fires when a new attachment is uploaded but auto-optimization is disabled.
+ *
+ * @since 1.8.4
+ *
+ * @param int $attachment_id Attachment ID.
+ * @param array $metadata An array of attachment meta data.
+ */
+ do_action( 'imagify_new_attachment_auto_optimization_disabled', $attachment_id, $metadata );
+
+ return $metadata;
+ }
+
+ /**
+ * Allow to prevent automatic optimization for a specific attachment.
+ *
+ * @since 1.6.12
+ *
+ * @param bool $optimize True to optimize, false otherwise.
+ * @param int $attachment_id Attachment ID.
+ * @param array $metadata An array of attachment meta data.
+ */
+ $optimize = apply_filters( 'imagify_auto_optimize_attachment', true, $attachment_id, $metadata );
+
+ if ( ! $optimize ) {
+ return $metadata;
+ }
+
+ /**
+ * It's a new upload and auto-optimization is enabled.
+ */
+ }
+
+ if ( ! $is_new_upload ) {
+ // An existing attachment being regenerated (or something).
+ $process = imagify_get_optimization_process( $attachment_id, 'wp' );
+
+ if ( ! $process->is_valid() ) {
+ // Uh?
+ return $metadata;
+ }
+
+ if ( ! $process->get_data()->get_optimization_status() ) {
+ /**
+ * Fires when an attachment is updated but not optimized yet.
+ *
+ * @since 1.8.4
+ *
+ * @param int $attachment_id Attachment ID.
+ * @param array $metadata An array of attachment meta data.
+ */
+ do_action( 'imagify_not_optimized_attachment_updated', $attachment_id, $metadata );
+
+ return $metadata;
+ }
+
+ /**
+ * Allow to prevent automatic reoptimization for a specific attachment.
+ *
+ * @since 1.8.4
+ *
+ * @param bool $optimize True to optimize, false otherwise.
+ * @param int $attachment_id Attachment ID.
+ * @param array $metadata An array of attachment meta data.
+ */
+ $optimize = apply_filters( 'imagify_auto_optimize_optimized_attachment', true, $attachment_id, $metadata );
+
+ if ( ! $optimize ) {
+ return $metadata;
+ }
+
+ /**
+ * The attachment already exists and was already optimized.
+ */
+ }
+
+ // Ready for the next step.
+ $this->set_step( $attachment_id, 'update' );
+
+ /**
+ * Triggered after a media auto-optimization init.
+ *
+ * @since 1.9.8
+ *
+ * @param int $attachment_id The media ID.
+ * @param bool $is_new_upload True if it's a new upload. False otherwize.
+ */
+ do_action( 'imagify_after_auto_optimization_init', $attachment_id, $is_new_upload );
+
+ return $metadata;
+ }
+
+ /**
+ * Launch auto optimization immediately after the post meta '_wp_attachment_metadata' is added or updated.
+ *
+ * @since 1.9
+ * @since 1.9 Previously named do_auto_optimization().
+ *
+ * @param int $meta_id ID of the metadata entry.
+ * @param int $attachment_id Current attachment ID.
+ * @param string $meta_key Meta key.
+ * @param mixed $metadata Meta value.
+ */
+ public function do_auto_optimization_after_meta_update( $meta_id, $attachment_id, $meta_key, $metadata ) {
+ if ( '_wp_attachment_metadata' !== $meta_key ) {
+ return;
+ }
+
+ if ( self::is_optimization_prevented( $attachment_id ) ) {
+ return;
+ }
+
+ if ( ! $this->has_step( $attachment_id, 'update' ) ) {
+ return;
+ }
+
+ $this->do_auto_optimization( $attachment_id, $this->has_step( $attachment_id, 'upload' ) );
+ }
+
+ /**
+ * Launch auto optimization immediately after the post meta '_wp_attachment_metadata' is added or updated.
+ *
+ * @since 1.8.4
+ * @since 1.9.8 Changed signature.
+ *
+ * @param int $attachment_id The media ID.
+ * @param bool $is_new_upload True if it's a new upload. False otherwize.
+ */
+ public function do_auto_optimization( $attachment_id, $is_new_upload ) {
+ $this->unset_steps( $attachment_id );
+
+ $process = imagify_get_optimization_process( $attachment_id, 'wp' );
+
+ /**
+ * Fires before an attachment auto-optimization is triggered.
+ *
+ * @since 1.8.4
+ *
+ * @param int $attachment_id The attachment ID.
+ * @param bool $is_new_upload True if it's a new upload. False otherwize.
+ */
+ do_action_deprecated( 'imagify_before_auto_optimization_launch', [ $attachment_id, $is_new_upload ], '1.9', 'imagify_before_auto_optimization' );
+
+ /**
+ * Triggered before a media is auto-optimized.
+ *
+ * @since 1.8.4
+ *
+ * @param int $attachment_id The media ID.
+ * @param bool $is_new_upload True if it's a new upload. False otherwize.
+ */
+ do_action( 'imagify_before_auto_optimization', $attachment_id, $is_new_upload );
+
+ if ( $is_new_upload ) {
+ /**
+ * It's a new upload.
+ */
+ // Optimize.
+ $process->optimize( null, [ 'is_new_upload' => 1 ] );
+ } else {
+ /**
+ * The media has already been optimized (or at least it has been tried).
+ */
+ $process_data = $process->get_data();
+
+ // Get the optimization level before deleting the optimization data.
+ $optimization_level = $process_data->get_optimization_level();
+
+ // Some specifics for the image editor.
+ if ( isset( $_POST['action'], $_POST['do'], $_POST['postid'] ) && 'image-editor' === $_POST['action'] && (int) $_POST['postid'] === $attachment_id ) { // WPCS: CSRF ok.
+ check_ajax_referer( 'image_editor-' . $attachment_id );
+
+ if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
+ imagify_die();
+ }
+
+ // Restore the backup file.
+ $result = $process->restore();
+
+ if ( is_wp_error( $result ) ) {
+ // Restoration failed, there is no good way to handle this case.
+ $process_data->delete_optimization_data();
+ }
+ } else {
+ // Remove old optimization data.
+ $process_data->delete_optimization_data();
+ }
+
+ // Optimize.
+ $process->optimize( $optimization_level );
+ }
+
+ /**
+ * Triggered after a media auto-optimization is launched.
+ *
+ * @since 1.8.4
+ *
+ * @param int $attachment_id The media ID.
+ * @param bool $is_new_upload True if it's a new upload. False otherwize.
+ */
+ do_action( 'imagify_after_auto_optimization', $attachment_id, $is_new_upload );
+ }
+
+ /**
+ * Remove the attachment ID from the $attachments property if the post meta '_wp_attachment_metadata' is deleted.
+ *
+ * @since 1.8.4
+ *
+ * @param int $meta_ids An array of deleted metadata entry IDs.
+ * @param int $attachment_id Current attachment ID.
+ * @param string $meta_key Meta key.
+ */
+ public function unset_optimization( $meta_ids, $attachment_id, $meta_key ) {
+ if ( '_wp_attachment_metadata' !== $meta_key ) {
+ return;
+ }
+
+ $this->unset_steps( $attachment_id );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** HOOKS FOR WP 5.3+âS UPLOAD FAILURE RECOVERING =========================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * With WP 5.3+, prevent auto-optimization when WP tries to create thumbnails after an upload error, because it triggers wp_update_attachment_metadata() for each thumbnail size.
+ *
+ * @since 1.9.8
+ * @see wp_ajax_media_create_image_subsizes()
+ * @see wp_update_image_subsizes()
+ */
+ public function prevent_auto_optimization_when_recovering_from_upload_failure() {
+ if ( ! check_ajax_referer( 'media-form', false, false ) ) {
+ return;
+ }
+
+ if ( ! current_user_can( 'upload_files' ) ) {
+ return;
+ }
+
+ if ( ! imagify_get_context( 'wp' )->current_user_can( 'auto-optimize' ) ) {
+ return;
+ }
+
+ $attachment_id = ! empty( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0;
+
+ if ( empty( $attachment_id ) ) {
+ return;
+ }
+
+ if ( ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
+ return;
+ }
+
+ $this->upload_failure_id = $attachment_id;
+
+ // Auto-optimization will be done on shutdown.
+ ob_start( [ $this, 'maybe_do_auto_optimization_after_recovering_from_upload_failure' ] );
+ }
+
+ /**
+ * Maybe launch auto-optimization after recovering from an upload failure, when all thumbnails are created.
+ *
+ * @since 1.9.8
+ * @see wp_ajax_media_create_image_subsizes()
+ *
+ * @param string $content Bufferâs content.
+ * @return string Bufferâs content.
+ */
+ public function maybe_do_auto_optimization_after_recovering_from_upload_failure( $content ) {
+ if ( empty( $content ) ) {
+ return $content;
+ }
+
+ if ( empty( $this->upload_failure_id ) ) {
+ // Uh?
+ return $content;
+ }
+
+ if ( ! get_post( $this->upload_failure_id ) ) {
+ return $content;
+ }
+
+ $json = @json_decode( $content );
+
+ if ( empty( $json->success ) ) {
+ return $content;
+ }
+
+ $attachment_id = $this->upload_failure_id;
+ $metadata = wp_get_attachment_metadata( $attachment_id );
+
+ // Launch the process.
+ $this->upload_failure_id = 0;
+ $this->set_step( $attachment_id, 'generate' );
+ $this->store_ids_to_optimize( $metadata, $attachment_id );
+
+ return $content;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Set a "step" for an attachment.
+ *
+ * @since 1.9.10
+ * @see $this->attachments
+ *
+ * @param int $attachment_id Current attachment ID.
+ * @param string $step The step to add.
+ */
+ public function set_step( $attachment_id, $step ) {
+ if ( empty( $this->attachments[ $attachment_id ] ) ) {
+ $this->attachments[ $attachment_id ] = [];
+ }
+
+ $this->attachments[ $attachment_id ][ $step ] = 1;
+ }
+
+ /**
+ * Unset a "step" for an attachment.
+ *
+ * @since 1.9.10
+ * @see $this->attachments
+ *
+ * @param int $attachment_id Current attachment ID.
+ * @param string $step The step to add.
+ */
+ public function unset_step( $attachment_id, $step ) {
+ unset( $this->attachments[ $attachment_id ][ $step ] );
+
+ if ( empty( $this->attachments[ $attachment_id ] ) ) {
+ $this->unset_steps( $attachment_id );
+ }
+ }
+
+ /**
+ * Unset all "steps" for an attachment.
+ *
+ * @since 1.9.10
+ * @see $this->attachments
+ *
+ * @param int $attachment_id Current attachment ID.
+ */
+ public function unset_steps( $attachment_id ) {
+ unset( $this->attachments[ $attachment_id ] );
+ }
+
+ /**
+ * Tell if a "step" for an attachment exists.
+ *
+ * @since 1.9.10
+ * @see $this->attachments
+ *
+ * @param int $attachment_id Current attachment ID.
+ * @param string $step The step to add.
+ * @return bool
+ */
+ public function has_step( $attachment_id, $step ) {
+ return ! empty( $this->attachments[ $attachment_id ][ $step ] );
+ }
+
+ /**
+ * Prevent an auto-optimization locally.
+ * How to use it:
+ * Imagify_Auto_Optimization::prevent_optimization( $attachment_id );
+ * wp_update_attachment_metadata( $attachment_id );
+ * Imagify_Auto_Optimization::allow_optimization( $attachment_id );
+ *
+ * @since 1.8.4
+ * @since 1.9.8 Prevents/Allows can stack.
+ *
+ * @param int $attachment_id Current attachment ID.
+ */
+ public static function prevent_optimization( $attachment_id ) {
+ if ( ! isset( self::$prevented[ $attachment_id ] ) ) {
+ self::$prevented[ $attachment_id ] = 1;
+ } else {
+ ++self::$prevented[ $attachment_id ];
+ }
+ }
+
+ /**
+ * Allow an auto-optimization locally.
+ * How to use it:
+ * Imagify_Auto_Optimization::prevent_optimization( $attachment_id );
+ * wp_update_attachment_metadata( $attachment_id );
+ * Imagify_Auto_Optimization::allow_optimization( $attachment_id );
+ *
+ * @since 1.8.4
+ * @since 1.9.8 Prevents/Allows can stack.
+ *
+ * @param int $attachment_id Current attachment ID.
+ */
+ public static function allow_optimization( $attachment_id ) {
+ if ( ! isset( self::$prevented[ $attachment_id ] ) ) {
+ return;
+ }
+ --self::$prevented[ $attachment_id ];
+
+ if ( self::$prevented[ $attachment_id ] <= 0 ) {
+ unset( self::$prevented[ $attachment_id ] );
+ }
+ }
+
+ /**
+ * Tell if an auto-optimization is prevented locally.
+ *
+ * @since 1.8.4
+ *
+ * @param int $attachment_id Current attachment ID.
+ * @return bool
+ */
+ public static function is_optimization_prevented( $attachment_id ) {
+ return ! empty( self::$prevented[ $attachment_id ] ) || ! empty( self::$prevented_internally[ $attachment_id ] );
+ }
+
+ /**
+ * Prevent an auto-optimization internally.
+ *
+ * @since 1.9.8
+ *
+ * @param int $attachment_id Current attachment ID.
+ */
+ protected static function prevent_optimization_internally( $attachment_id ) {
+ self::$prevented_internally[ $attachment_id ] = 1;
+ }
+
+ /**
+ * Allow an auto-optimization internally.
+ *
+ * @since 1.9.8
+ *
+ * @param int $attachment_id Current attachment ID.
+ */
+ protected static function allow_optimization_internally( $attachment_id ) {
+ unset( self::$prevented_internally[ $attachment_id ] );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-cron-library-size.php b/wp-content/plugins/imagify/inc/classes/class-imagify-cron-library-size.php
new file mode 100644
index 00000000..ee7918ed
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-cron-library-size.php
@@ -0,0 +1,91 @@
+ 'imagify_update_estimate_sizes',
+ '_ajax_nonce' => wp_create_nonce( 'update_estimate_sizes' ),
+ ) );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-cron-rating.php b/wp-content/plugins/imagify/inc/classes/class-imagify-cron-rating.php
new file mode 100644
index 00000000..6f9c618e
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-cron-rating.php
@@ -0,0 +1,110 @@
+get_event_name() ) && ! get_site_transient( 'do_imagify_rating_cron' ) ) {
+ wp_schedule_event( $this->get_event_timestamp(), $this->get_event_recurrence(), $this->get_event_name() );
+ }
+ }
+
+ /**
+ * The event action.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function do_event() {
+ // Stop the process if the plugin isn't installed for 3 days.
+ if ( get_site_transient( 'imagify_seen_rating_notice' ) ) {
+ return;
+ }
+
+ $user = get_imagify_user();
+
+ if ( ! is_wp_error( $user ) && (int) $user->image_count > 100 ) {
+ set_site_transient( 'imagify_user_images_count', $user->image_count );
+ }
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-cron-sync-files.php b/wp-content/plugins/imagify/inc/classes/class-imagify-cron-sync-files.php
new file mode 100644
index 00000000..3e641317
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-cron-sync-files.php
@@ -0,0 +1,116 @@
+can_operate() || ! $files_db->can_operate() ) {
+ return;
+ }
+
+ if ( ! Imagify_Requirements::is_api_key_valid() ) {
+ return;
+ }
+
+ if ( Imagify_Requirements::is_over_quota() ) {
+ return;
+ }
+
+ @set_time_limit( 0 );
+
+ /**
+ * Get the folders from DB.
+ */
+ $folders = Imagify_Custom_Folders::get_folders();
+
+ if ( ! $folders ) {
+ return;
+ }
+
+ Imagify_Custom_Folders::synchronize_files_from_folders( $folders );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-custom-folders.php b/wp-content/plugins/imagify/inc/classes/class-imagify-custom-folders.php
new file mode 100644
index 00000000..01fbd4a7
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-custom-folders.php
@@ -0,0 +1,1316 @@
+get_site_root() . 'imagify-backup/';
+
+ /**
+ * Filter the backup directory path (custom folders).
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param string $backup_dir The backup directory path.
+ */
+ $backup_dir = apply_filters( 'imagify_files_backup_directory', $backup_dir );
+ $backup_dir = $filesystem->normalize_dir_path( $backup_dir );
+
+ return $backup_dir;
+ }
+
+ /**
+ * Tell if the folder containing the backups is writable (custom folders).
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public static function backup_dir_is_writable() {
+ return imagify_get_filesystem()->make_dir( self::get_backup_dir_path() );
+ }
+
+ /**
+ * Get the backup path of a specific file (custom folders).
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return string|bool The backup path. False on failure.
+ */
+ public static function get_file_backup_path( $file_path ) {
+ $file_path = wp_normalize_path( (string) $file_path );
+ $site_root = imagify_get_filesystem()->get_site_root();
+ $backup_dir = self::get_backup_dir_path();
+
+ if ( ! $file_path ) {
+ return false;
+ }
+
+ return preg_replace( '@^' . preg_quote( $site_root, '@' ) . '@', $backup_dir, $file_path );
+ }
+
+ /**
+ * Add index.php files recursively to a given directory and all its subdirectories.
+ *
+ * @since 1.9.11
+ *
+ * @param string $backup_dir (optional) Path to the directory where we will start adding indexes.
+ * Defaults to custom-folders backup dir.
+ *
+ * @return void
+ */
+ public static function add_indexes( $backup_dir = '' ) {
+ $filesystem = Imagify_Filesystem::get_instance();
+
+ if ( empty( $backup_dir ) ) {
+ $backup_dir = self::get_backup_dir_path();
+ }
+
+ if ( ! $filesystem->is_writable( $backup_dir ) ) {
+ return;
+ }
+
+ try {
+ $directory = new RecursiveDirectoryIterator( $backup_dir );
+ $iterator = new RecursiveIteratorIterator( $directory );
+
+ foreach ( $iterator as $fileinfo ) {
+
+ if ( '.' !== $fileinfo->getFilename() ) {
+ continue;
+ }
+
+ $path = trailingslashit( $fileinfo->getRealPath() );
+
+ if ( ! $filesystem->is_file( $path . 'index.html' )
+ && ! $filesystem->is_file( $path . 'index.php' )
+ ) {
+ $filesystem->touch( $path . 'index.php' );
+ }
+ }
+ } catch ( Exception $e ) {
+ return;
+ }
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** SINGLE FILE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Insert a file into the DB.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $args An array of arguments to pass to Imagify_Files_DB::insert(). Required values are 'folder_id' and ( 'path' or 'file_path').
+ * @return int The file ID on success. 0 on failure.
+ */
+ public static function insert_file( $args = array() ) {
+ if ( empty( $args['folder_id'] ) ) {
+ return 0;
+ }
+
+ if ( empty( $args['path'] ) ) {
+ if ( empty( $args['file_path'] ) ) {
+ return 0;
+ }
+
+ $args['path'] = Imagify_Files_Scan::add_placeholder( $args['file_path'] );
+ }
+
+ if ( empty( $args['file_path'] ) ) {
+ $args['file_path'] = Imagify_Files_Scan::remove_placeholder( $args['path'] );
+ }
+
+ $filesystem = imagify_get_filesystem();
+
+ if ( ! $filesystem->is_readable( $args['file_path'] ) ) {
+ return 0;
+ }
+
+ if ( empty( $args['file_date'] ) || '0000-00-00 00:00:00' === $args['file_date'] ) {
+ $args['file_date'] = $filesystem->get_date( $args['file_path'] );
+ }
+
+ if ( empty( $args['mime_type'] ) ) {
+ $args['mime_type'] = $filesystem->get_mime_type( $args['file_path'] );
+ }
+
+ if ( ( empty( $args['width'] ) || empty( $args['height'] ) ) && strpos( $args['mime_type'], 'image/' ) === 0 ) {
+ $file_size = $filesystem->get_image_size( $args['file_path'] );
+ $args['width'] = $file_size ? $file_size['width'] : 0;
+ $args['height'] = $file_size ? $file_size['height'] : 0;
+ }
+
+ if ( empty( $args['hash'] ) ) {
+ $args['hash'] = md5_file( $args['file_path'] );
+ }
+
+ if ( empty( $args['original_size'] ) ) {
+ $args['original_size'] = (int) $filesystem->size( $args['file_path'] );
+ }
+
+ $files_db = Imagify_Files_DB::get_instance();
+ $primary_key = $files_db->get_primary_key();
+ unset( $args[ $primary_key ] );
+
+ return $files_db->insert( $args );
+ }
+
+ /**
+ * Delete a custom file.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $args An array of arguments.
+ * At least: 'file_id'. At best: 'file_id', 'file_path' (or 'path' for the placeholder), and 'backup_path'.
+ */
+ public static function delete_file( $args = [] ) {
+ $args = array_merge( [
+ 'file_id' => 0,
+ 'file_path' => '',
+ 'path' => '',
+ 'backup_path' => '',
+ 'process' => false,
+ ], $args );
+
+ $filesystem = imagify_get_filesystem();
+
+ // Fill the blanks.
+ if ( $args['process'] && $args['process'] instanceof \Imagify\Optimization\Process\ProcessInterface ) {
+ $process = $args['process'];
+ } else {
+ $process = imagify_get_optimization_process( $args['file_id'], 'custom-folders' );
+ }
+
+ if ( ! $process->is_valid() ) {
+ // You fucked up!
+ return;
+ }
+
+ if ( ! $args['file_path'] && $args['path'] ) {
+ $args['file_path'] = Imagify_Files_Scan::remove_placeholder( $args['path'] );
+ }
+
+ if ( ! $args['file_path'] && $args['file_id'] ) {
+ $args['file_path'] = $process->get_media()->get_fullsize_path();
+ }
+
+ if ( ! $args['backup_path'] && $args['file_path'] ) {
+ $args['backup_path'] = self::get_file_backup_path( $args['file_path'] );
+ }
+
+ if ( ! $args['backup_path'] && $args['file_id'] ) {
+ $args['backup_path'] = $process->get_media()->get_raw_backup_path();
+ }
+
+ // Trigger a common hook.
+ imagify_trigger_delete_media_hook( $process );
+
+ // The file.
+ if ( $args['file_path'] && $filesystem->exists( $args['file_path'] ) ) {
+ $filesystem->delete( $args['file_path'] );
+ }
+
+ // The backup file.
+ if ( $args['backup_path'] && $filesystem->exists( $args['backup_path'] ) ) {
+ $filesystem->delete( $args['backup_path'] );
+ }
+
+ // Webp.
+ $mime_type = $filesystem->get_mime_type( $args['file_path'] );
+ $is_image = $mime_type && strpos( $mime_type, 'image/' ) === 0;
+ $webp_path = $is_image ? imagify_path_to_webp( $args['file_path'] ) : false;
+
+ if ( $webp_path && $filesystem->is_writable( $webp_path ) ) {
+ $filesystem->delete( $webp_path );
+ }
+
+ // In the database.
+ $process->get_media()->delete_row();
+ }
+
+ /**
+ * Check if a file has been modified, and update the database accordingly.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process A \Imagify\Optimization\Process\ProcessInterface object.
+ * @param bool $is_folder_active Tell if the folder is active.
+ * @return int|bool|object The file ID if modified. False if not modified. A WP_Error object if the entry has been removed from the database.
+ * The entry is removed from the database if:
+ * - The file doesn't exist anymore.
+ * - Or if its folder is not active and: the file has been modified, or the file is not optimized by Imagify, or the file is orphan (its folder is not in the database anymore).
+ */
+ public static function refresh_file( $process, $is_folder_active = null ) {
+ global $wpdb;
+
+ if ( ! $process->is_valid() ) {
+ return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ $filesystem = imagify_get_filesystem();
+ $media = $process->get_media();
+ $file_path = $media->get_fullsize_path();
+ $mime_type = $filesystem->get_mime_type( $file_path );
+ $is_image = $mime_type && strpos( $mime_type, 'image/' ) === 0;
+ $webp_path = $is_image ? imagify_path_to_webp( $file_path ) : false;
+ $has_webp = $webp_path && $filesystem->is_writable( $webp_path );
+ $modified = false;
+
+ if ( ! $file_path || ! $filesystem->exists( $file_path ) ) {
+ /**
+ * The file doesn't exist anymore.
+ */
+ // Delete the backup file.
+ $process->delete_backup();
+
+ // Get the folder ID before removing the row.
+ $folder_id = $media->get_row();
+ $folder_id = $folder_id['folder_id'];
+
+ // Remove the entry from the database.
+ $media->delete_row();
+
+ // Remove the corresponding folder if inactive and have no files left.
+ self::remove_empty_inactive_folders( $folder_id );
+
+ // Delete the webp version.
+ if ( $has_webp ) {
+ $filesystem->delete( $webp_path );
+ }
+
+ return new WP_Error( 'no-file', __( 'The file was missing or its path could not be retrieved from the database. The entry has been deleted from the database.', 'imagify' ) );
+ }
+
+ /**
+ * The file still exists.
+ */
+ $old_data = $media->get_row();
+ $new_data = [];
+
+ // Folder ID.
+ if ( $old_data['folder_id'] ) {
+ $folder = wp_cache_get( 'custom_folder_' . $old_data['folder_id'], 'imagify' );
+
+ if ( false === $folder ) {
+ // The folder is not in the cache.
+ $folder = Imagify_Folders_DB::get_instance()->get( $old_data['folder_id'] );
+ $folder = $folder ? $folder : 0;
+ }
+
+ if ( ! $folder ) {
+ // The folder is not in the database anymore.
+ $old_data['folder_id'] = 0;
+ $new_data['folder_id'] = 0;
+ }
+ } else {
+ $folder = 0;
+ }
+
+ // Hash + modified.
+ $current_hash = md5_file( $file_path );
+
+ if ( ! $old_data['hash'] ) {
+ $new_data['modified'] = 0;
+ } else {
+ $new_data['modified'] = (int) ! hash_equals( $old_data['hash'], $current_hash );
+ }
+
+ // The file is modified or is not optimized.
+ if ( $new_data['modified'] || ! $process->get_data()->is_optimized() ) {
+ if ( ! isset( $is_folder_active ) ) {
+ $is_folder_active = $folder && $folder['active'];
+ }
+
+ // Its folder is not active: remove the entry from the database and delete the backup.
+ if ( ! $is_folder_active ) {
+ // Delete the backup file.
+ $process->delete_backup();
+
+ // Remove the entry from the database.
+ $media->delete_row();
+
+ // Remove the corresponding folder if inactive and have no files left.
+ if ( $old_data['folder_id'] ) {
+ self::remove_empty_inactive_folders( $old_data['folder_id'] );
+ }
+
+ // Delete the webp version.
+ if ( $has_webp ) {
+ $filesystem->delete( $webp_path );
+ }
+
+ return new WP_Error( 'folder-not-active', __( 'The file has been modified or was not optimized: its folder not being selected in the settings, the entry has been deleted from the database.', 'imagify' ) );
+ }
+ }
+
+ $new_data['hash'] = $current_hash;
+
+ // The file is modified.
+ if ( $new_data['modified'] ) {
+ // Delete all optimization data and update file data.
+ $modified = true;
+ $mime_type = ! empty( $old_data['mime_type'] ) ? $old_data['mime_type'] : $filesystem->get_mime_type( $file_path );
+
+ if ( $is_image ) {
+ $size = $filesystem->get_image_size( $file_path );
+
+ // Delete the webp version.
+ if ( $has_webp ) {
+ $filesystem->delete( $webp_path );
+ }
+ } else {
+ $size = false;
+ }
+
+ $new_data = array_merge( $new_data, [
+ 'file_date' => $filesystem->get_date( $file_path ),
+ 'width' => $size ? $size['width'] : 0,
+ 'height' => $size ? $size['height'] : 0,
+ 'original_size' => $filesystem->size( $file_path ),
+ 'optimized_size' => null,
+ 'percent' => null,
+ 'optimization_level' => null,
+ 'status' => null,
+ 'error' => null,
+ 'data' => [],
+ ] );
+
+ // Delete the backup of the previous file.
+ $process->delete_backup();
+ } else {
+ // Update file data to make sure nothing is missing.
+ $backup_path = $media->get_backup_path();
+ $path = $backup_path ? $backup_path : $file_path;
+ $mime_type = ! empty( $old_data['mime_type'] ) ? $old_data['mime_type'] : $filesystem->get_mime_type( $path );
+ $file_date = ! empty( $old_data['file_date'] ) && '0000-00-00 00:00:00' !== $old_data['file_date'] ? $old_data['file_date'] : $filesystem->get_date( $path );
+
+ if ( $is_image ) {
+ $size = $filesystem->get_image_size( $path );
+ } else {
+ $size = false;
+ }
+
+ $new_data = array_merge( $new_data, [
+ 'file_date' => $file_date,
+ 'width' => $size ? $size['width'] : 0,
+ 'height' => $size ? $size['height'] : 0,
+ 'original_size' => $filesystem->size( $path ),
+ ] );
+
+ // Webp.
+ $webp_size = 'full' . $process::WEBP_SUFFIX;
+
+ if ( $has_webp && empty( $old_data['data'][ $webp_size ]['success'] ) ) {
+ $webp_file_size = $filesystem->size( $webp_path );
+
+ $old_data['data'][ $webp_size ] = [
+ 'success' => true,
+ 'original_size' => $new_data['original_size'],
+ 'optimized_size' => $webp_file_size,
+ 'percent' => round( ( ( $new_data['original_size'] - $webp_file_size ) / $new_data['original_size'] ) * 100, 2 ),
+ ];
+ }
+ }
+
+ // Save the new data.
+ $old_data = array_intersect_key( $old_data, $new_data );
+ ksort( $old_data );
+ ksort( $new_data );
+
+ if ( $old_data !== $new_data ) {
+ $media->update_row( $new_data );
+ }
+
+ return $modified ? $media->get_id() : false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** FOLDERS AND FILES ======================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get folders from the DB.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $args A list of arguments to tell more precisely what to fetch:
+ * - bool $active True to fetch only "active" folders (checked in the settings). False to fetch only folders that are not "active".
+ * @return array An array of arrays containing the following values:
+ * - int $folder_id The folder ID.
+ * - string $path The folder path, with placeholder.
+ * - int $active 1 if the folder should be optimized. 0 otherwize.
+ * - string $folder_path The real absolute folder path.
+ * Example:
+ * Array(
+ * [7] => Array(
+ * [folder_id] => 7
+ * [path] => {{ROOT}}/custom-path/
+ * [active] => 1
+ * [folder_path] => /absolute/path/to/custom-path/
+ * )
+ * [13] => Array(
+ * [folder_id] => 13
+ * [path] => {{CONTENT}}/another-custom-path/
+ * [active] => 1
+ * [folder_path] => /absolute/path/to/wp-content/another-custom-path/
+ * )
+ * )
+ */
+ public static function get_folders( $args = array() ) {
+ global $wpdb;
+
+ $folders_db = Imagify_Folders_DB::get_instance();
+ $folders_table = $folders_db->get_table_name();
+ $primary_key = $folders_db->get_primary_key();
+ $where_active = '';
+
+ if ( isset( $args['active'] ) ) {
+ if ( $args['active'] ) {
+ $args['active'] = true;
+ $where_active = 'WHERE active = 1';
+ } else {
+ $args['active'] = false;
+ $where_active = 'WHERE active = 0';
+ }
+ }
+
+ // Get the folders from the DB.
+ $results = $wpdb->get_results( "SELECT * FROM $folders_table $where_active;", ARRAY_A ); // WPCS: unprepared SQL ok.
+
+ if ( ! $results || ! is_array( $results ) ) {
+ return array();
+ }
+
+ // Cast results, add absolute paths.
+ $folders = array();
+
+ foreach ( $results as $row_fields ) {
+ // Cast the row.
+ $row_fields = $folders_db->cast_row( $row_fields );
+
+ // Add the absolute path.
+ $row_fields['folder_path'] = Imagify_Files_Scan::remove_placeholder( $row_fields['path'] );
+
+ // Add the row to the list.
+ $folders[ $row_fields[ $primary_key ] ] = $row_fields;
+ }
+
+ return $folders;
+ }
+
+ /**
+ * Get files belonging to the given folders.
+ * Files are scanned from the folders, then:
+ * - If a file doesn't exist in the DB, it is added (maybe, depending on arguments provided).
+ * - If a file is in the DB, but with a wrong folder_id, it is fixed.
+ * - If a file doesn't exist, it is removed from the database and its backup is deleted.
+ *
+ * @since 1.7
+ * @access public
+ * @see Imagify_Custom_Folders::get_folders()
+ * @author Grégory Viguier
+ *
+ * @param array $folders An array of arrays containing at least the keys 'folder_path' and 'active'. See Imagify_Custom_Folders::get_folders() for the format.
+ * @param array $args A list of arguments to tell more precisely what to fetch:
+ * - int $optimization_level If set with an integer, only files that needs to be optimized to this level will be returned (the status is also checked).
+ * - bool $return_only_old_files True to return only files that have not been newly inserted.
+ * - bool $add_inactive_folder_files When true: if a file is not in the database and its folder is not "active", it is added to the DB. Default false: new files are not added to the database if the folder is not active.
+ * @return array A list of files in the following format:
+ * Array(
+ * [_2] => Array(
+ * [file_id] => 2
+ * [folder_id] => 7
+ * [path] => {{ROOT}}/custom-path/image-1.jpg
+ * [optimization_level] => null
+ * [status] => null
+ * [file_path] => /absolute/path/to/custom-path/image-1.jpg
+ * ),
+ * [_3] => Array(
+ * [file_id] => 3
+ * [folder_id] => 7
+ * [path] => {{ROOT}}/custom-path/image-2.jpg
+ * [optimization_level] => 2
+ * [status] => success
+ * [file_path] => /absolute/path/to/custom-path/image-2.jpg
+ * ),
+ * [_6] => Array(
+ * [file_id] => 6
+ * [folder_id] => 13
+ * [path] => {{CONTENT}}/another-custom-path/image-1.jpg
+ * [optimization_level] => 0
+ * [status] => error
+ * [file_path] => /absolute/path/to/wp-content/another-custom-path/image-1.jpg
+ * ),
+ * )
+ * The fields 'optimization_level' and 'status' are set only if the argument 'optimization_level' was set.
+ */
+ public static function get_files_from_folders( $folders, $args = array() ) {
+ global $wpdb;
+
+ if ( ! $folders ) {
+ return array();
+ }
+
+ $filesystem = imagify_get_filesystem();
+ $files_db = Imagify_Files_DB::get_instance();
+ $files_table = $files_db->get_table_name();
+ $files_key = $files_db->get_primary_key();
+ $files_key_esc = esc_sql( $files_key );
+
+ $optimization = isset( $args['optimization_level'] ) && is_numeric( $args['optimization_level'] );
+ $no_new_files = ! empty( $args['return_only_old_files'] );
+ $add_inactive_folder_files = ! empty( $args['add_inactive_folder_files'] );
+
+ /**
+ * Scan folders for files. $files_from_scan will be in the following format:
+ * Array(
+ * [7] => Array(
+ * [/absolute/path/to/custom-path/image-1.jpg] => 0
+ * [/absolute/path/to/custom-path/image-2.jpg] => 1
+ * )
+ * [13] => Array(
+ * [/absolute/path/to/wp-content/another-custom-path/image-1.jpg] => 0
+ * [/absolute/path/to/wp-content/another-custom-path/image-2.jpg] => 1
+ * [/absolute/path/to/wp-content/another-custom-path/image-3.jpg] => 2
+ * )
+ * )
+ */
+ $files_from_scan = array();
+
+ foreach ( $folders as $folder_id => $folder ) {
+ $files_from_scan[ $folder_id ] = Imagify_Files_Scan::get_files_from_folder( $folder['folder_path'] );
+
+ if ( is_wp_error( $files_from_scan[ $folder_id ] ) ) {
+ unset( $files_from_scan[ $folder_id ] );
+ }
+ }
+
+ $files_from_scan = array_map( 'array_flip', $files_from_scan );
+
+ /**
+ * Get the files from DB. $files_from_db will be in the same format as the function output.
+ */
+ $already_optimized = array();
+ $folder_ids = array_keys( $folders );
+ $files_from_db = array_fill_keys( $folder_ids, array() );
+ $folder_ids = Imagify_DB::prepare_values_list( $folder_ids );
+ $select_fields = "$files_key_esc, folder_id, path" . ( $optimization ? ', optimization_level, status' : '' );
+
+ if ( $optimization ) {
+ $orderby = "
+ CASE status
+ WHEN 'already_optimized' THEN 3
+ WHEN 'error' THEN 2
+ ELSE 1
+ END ASC,
+ $files_key_esc DESC";
+ } else {
+ $orderby = "folder_id, $files_key_esc";
+ }
+
+ $results = $wpdb->get_results( "SELECT $select_fields FROM $files_table WHERE folder_id IN ( $folder_ids ) ORDER BY $orderby;", ARRAY_A ); // WPCS: unprepared SQL ok.
+
+ if ( $results ) {
+ $wpdb->flush();
+
+ foreach ( $results as $i => $row_fields ) {
+ // Cast the row.
+ $row_fields = $files_db->cast_row( $row_fields );
+
+ // Add the absolute path.
+ $row_fields['file_path'] = Imagify_Files_Scan::remove_placeholder( $row_fields['path'] );
+
+ // Remove the file from the scan.
+ unset( $files_from_scan[ $row_fields['folder_id'] ][ $row_fields['file_path'] ] );
+
+ if ( $optimization ) {
+ if ( 'error' !== $row_fields['status'] && $row_fields['optimization_level'] === $args['optimization_level'] ) {
+ // Try the same level only if the status is an error.
+ continue;
+ }
+
+ if ( 'already_optimized' === $row_fields['status'] && $row_fields['optimization_level'] >= $args['optimization_level'] ) {
+ // If the image is already compressed, optimize only if the requested level is higher.
+ continue;
+ }
+
+ if ( 'success' === $row_fields['status'] && $args['optimization_level'] !== $row_fields['optimization_level'] ) {
+ $file_backup_path = self::get_file_backup_path( $row_fields['file_path'] );
+
+ if ( ! $file_backup_path || ! $filesystem->exists( $file_backup_path ) ) {
+ // Don't try to re-optimize if there is no backup file.
+ continue;
+ }
+ }
+ }
+
+ if ( ! $filesystem->exists( $row_fields['file_path'] ) ) {
+ // If the file doesn't exist: remove all traces of it and bail out.
+ self::delete_file( array(
+ 'file_id' => $row_fields[ $files_key ],
+ 'file_path' => $row_fields['file_path'],
+ ) );
+ continue;
+ }
+
+ if ( $optimization && 'already_optimized' === $row_fields['status'] ) {
+ $already_optimized[ '_' . $row_fields[ $files_key ] ] = 1;
+ }
+
+ // Add the row to the list.
+ $files_from_db[ $row_fields['folder_id'] ][ '_' . $row_fields[ $files_key ] ] = $row_fields;
+ }
+ }
+
+ unset( $results );
+ $files_from_scan = array_filter( $files_from_scan );
+
+ // Make sure files from the scan are not already in the DB with another folder (shouldn't be possible, but, you know...).
+ if ( $files_from_scan ) {
+ $folders_by_placeholder = array();
+
+ foreach ( $files_from_scan as $folder_id => $folder_files ) {
+ foreach ( $folder_files as $file_path => $i ) {
+ $placeholder = Imagify_Files_Scan::add_placeholder( $file_path );
+
+ $folders_by_placeholder[ $placeholder ] = $folder_id;
+ $files_from_scan[ $folder_id ][ $file_path ] = $placeholder;
+ }
+ }
+
+ $placeholders = Imagify_DB::prepare_values_list( array_keys( $folders_by_placeholder ) );
+ $select_fields = "$files_key_esc, folder_id, path" . ( $optimization ? ', optimization_level, status' : '' );
+
+ $results = $wpdb->get_results( "SELECT $select_fields FROM $files_table WHERE path IN ( $placeholders ) ORDER BY folder_id, $files_key_esc;", ARRAY_A ); // WPCS: unprepared SQL ok.
+
+ if ( $results ) {
+ // Damn...
+ $wpdb->flush();
+
+ foreach ( $results as $i => $row_fields ) {
+ // Cast the row.
+ $row_fields = $files_db->cast_row( $row_fields );
+ $old_folder_id = $row_fields['folder_id'];
+
+ // Add the absolute path.
+ $row_fields['file_path'] = Imagify_Files_Scan::remove_placeholder( $row_fields['path'] );
+
+ // Set the new folder ID.
+ $row_fields['folder_id'] = $folders_by_placeholder[ $row_fields['path'] ];
+
+ // Remove the file from everywhere.
+ unset(
+ $files_from_db[ $old_folder_id ][ '_' . $row_fields[ $files_key ] ],
+ $files_from_scan[ $old_folder_id ][ $row_fields['file_path'] ],
+ $files_from_scan[ $row_fields['folder_id'] ][ $row_fields['file_path'] ]
+ );
+
+ if ( $optimization ) {
+ if ( 'error' !== $row_fields['status'] && $row_fields['optimization_level'] === $args['optimization_level'] ) {
+ // Try the same level only if the status is an error.
+ continue;
+ }
+
+ if ( 'already_optimized' === $row_fields['status'] && $row_fields['optimization_level'] >= $args['optimization_level'] ) {
+ // If the image is already compressed, optimize only if the requested level is higher.
+ continue;
+ }
+
+ if ( 'success' === $row_fields['status'] && $args['optimization_level'] !== $row_fields['optimization_level'] ) {
+ $file_backup_path = self::get_file_backup_path( $row_fields['file_path'] );
+
+ if ( ! $file_backup_path || ! $filesystem->exists( $file_backup_path ) ) {
+ // Don't try to re-optimize if there is no backup file.
+ continue;
+ }
+ }
+ }
+
+ if ( ! $filesystem->exists( $row_fields['file_path'] ) ) {
+ // If the file doesn't exist: remove all traces of it and bail out.
+ self::delete_file( array(
+ 'file_id' => $row_fields[ $files_key ],
+ 'file_path' => $row_fields['file_path'],
+ ) );
+ continue;
+ }
+
+ // Set the correct folder ID in the DB.
+ $success = $files_db->update( $row_fields[ $files_key ], array(
+ 'folder_id' => $row_fields['folder_id'],
+ ) );
+
+ if ( $success ) {
+ if ( $optimization && 'already_optimized' === $row_fields['status'] ) {
+ $already_optimized[ '_' . $row_fields[ $files_key ] ] = 1;
+ }
+
+ $files_from_db[ $row_fields['folder_id'] ][ '_' . $row_fields[ $files_key ] ] = $row_fields;
+ }
+ }
+ }
+
+ unset( $results, $folders_by_placeholder );
+ }
+
+ $files_from_scan = array_filter( $files_from_scan );
+
+ // Insert the remaining files into the DB.
+ if ( $files_from_scan ) {
+ foreach ( $files_from_scan as $folder_id => $placeholders ) {
+ // Don't add the file to the DB if its folder is not "active".
+ if ( ! $add_inactive_folder_files && empty( $folders[ $folder_id ]['active'] ) ) {
+ unset( $files_from_scan[ $folder_id ] );
+ continue;
+ }
+
+ foreach ( $placeholders as $file_path => $placeholder ) {
+ $file_id = self::insert_file( array(
+ 'folder_id' => $folder_id,
+ 'path' => $placeholder,
+ 'file_path' => $file_path,
+ ) );
+
+ if ( $file_id && ! $no_new_files ) {
+ $files_from_db[ $folder_id ][ '_' . $file_id ] = array(
+ 'file_id' => $file_id,
+ 'folder_id' => $folder_id,
+ 'path' => $placeholder,
+ 'optimization_level' => null,
+ 'status' => null,
+ 'file_path' => $file_path,
+ );
+ }
+ }
+
+ unset( $files_from_scan[ $folder_id ] );
+ }
+ }
+
+ $files_from_db = array_filter( $files_from_db );
+
+ if ( ! $files_from_db ) {
+ return array();
+ }
+
+ $files_from_db = call_user_func_array( 'array_merge', $files_from_db );
+
+ if ( $already_optimized ) {
+ // Put the files already optimized at the end of the list.
+ $already_optimized = array_intersect_key( $files_from_db, $already_optimized );
+ $files_from_db = array_diff_key( $files_from_db, $already_optimized );
+ $files_from_db = array_merge( $files_from_db, $already_optimized );
+ }
+
+ return $files_from_db;
+ }
+
+ /**
+ * Check if files inside the given folders have been modified, and update the database accordingly.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $folders A list of folders. See Imagify_Custom_Folders::get_folders() for the format.
+ */
+ public static function synchronize_files_from_folders( $folders ) {
+ global $wpdb;
+ /**
+ * Get the files from DB, and from the folder.
+ */
+ $files = self::get_files_from_folders( $folders, array(
+ 'return_only_old_files' => true,
+ ) );
+
+ if ( ! $files ) {
+ // This folder doesn't have (new) images.
+ return;
+ }
+
+ $files_db = Imagify_Files_DB::get_instance();
+ $files_table = $files_db->get_table_name();
+ $files_key = $files_db->get_primary_key();
+ $files_key_esc = esc_sql( $files_key );
+ $file_ids = wp_list_pluck( $files, $files_key );
+ $file_ids = Imagify_DB::prepare_values_list( $file_ids );
+ $results = $wpdb->get_results( "SELECT * FROM $files_table WHERE $files_key IN ( $file_ids ) ORDER BY $files_key_esc;", ARRAY_A ); // WPCS: unprepared SQL ok.
+
+ if ( ! $results ) {
+ // WAT?!
+ return;
+ }
+
+ // Caching the folders will prevent unecessary SQL queries in Imagify_Custom_Folders::refresh_file().
+ foreach ( $folders as $folder_id => $folder ) {
+ wp_cache_set( 'custom_folder_' . $folder_id, $folder, 'imagify' );
+ }
+
+ // Finally, refresh the files data.
+ foreach ( $results as $file ) {
+ $file = $files_db->cast_row( $file );
+ $folder_id = $file['folder_id'];
+ $process = imagify_get_optimization_process( $file, 'custom-folders' );
+
+ self::refresh_file( $process, $folders[ $folder_id ]['active'] );
+ }
+
+ foreach ( $folders as $folder_id => $folder ) {
+ wp_cache_delete( 'custom_folder_' . $folder_id, 'imagify' );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WHEN SAVING SELECTED FOLDERS ============================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Dectivate all active folders.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public static function deactivate_all_folders() {
+ self::deactivate_not_selected_folders();
+ }
+
+ /**
+ * Dectivate folders that are not selected.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array|object|string $selected_paths A list of "placeholdered" paths corresponding to the selected folders.
+ */
+ public static function deactivate_not_selected_folders( $selected_paths = array() ) {
+ global $wpdb;
+
+ $folders_table = Imagify_Folders_DB::get_instance()->get_table_name();
+
+ if ( $selected_paths ) {
+ if ( is_array( $selected_paths ) || is_object( $selected_paths ) ) {
+ $selected_paths = Imagify_DB::prepare_values_list( $selected_paths );
+ }
+
+ $selected_paths_clause = "AND path NOT IN ( $selected_paths )";
+ } else {
+ $selected_paths_clause = '';
+ }
+
+ // Remove the active status from the folders that are not selected.
+ $wpdb->query( "UPDATE $folders_table SET active = 0 WHERE active != 0 $selected_paths_clause" ); // WPCS: unprepared SQL ok.
+ }
+
+ /**
+ * Activate folders that are selected.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array|object $selected_paths A list of "placeholdered" paths corresponding to the selected folders.
+ * @return array An array of paths of folders that are not in the DB.
+ */
+ public static function activate_selected_folders( $selected_paths ) {
+ global $wpdb;
+
+ if ( ! $selected_paths ) {
+ return $selected_paths;
+ }
+
+ $folders_db = Imagify_Folders_DB::get_instance();
+ $folders_table = $folders_db->get_table_name();
+ $folders_key = $folders_db->get_primary_key();
+
+ $selected_paths = (array) $selected_paths;
+ $selected_in = Imagify_DB::prepare_values_list( $selected_paths );
+
+ // Get folders that already are in the DB.
+ $folders = $wpdb->get_results( "SELECT * FROM $folders_table WHERE path IN ( $selected_in );", ARRAY_A ); // WPCS: unprepared SQL ok.
+
+ if ( ! $folders ) {
+ return $selected_paths;
+ }
+
+ $selected_paths = array_flip( $selected_paths );
+
+ foreach ( $folders as $folder ) {
+ $folder = $folders_db->cast_row( $folder );
+
+ if ( Imagify_Files_Scan::placeholder_path_exists( $folder['path'] ) ) {
+ if ( ! $folder['active'] ) {
+ // Add the active status only if not already set and if the folder exists.
+ $folders_db->update( $folder[ $folders_key ], array(
+ 'active' => 1,
+ ) );
+ }
+ } else {
+ // Remove the active status if the folder does not exist.
+ $folders_db->update( $folder[ $folders_key ], array(
+ 'active' => 0,
+ ) );
+ }
+
+ // Remove the path from the selected list, so the remaining will be created.
+ unset( $selected_paths[ $folder['path'] ] );
+ }
+
+ // Paths of folders that are not in the DB.
+ return array_flip( $selected_paths );
+ }
+
+ /**
+ * Insert folders into the database.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $folders An array of "placeholdered" paths.
+ * @return array An array of folder IDs.
+ */
+ public static function insert_folders( $folders ) {
+ if ( ! $folders ) {
+ return array();
+ }
+
+ $folder_ids = array();
+ $filesystem = imagify_get_filesystem();
+ $folders_db = Imagify_Folders_DB::get_instance();
+
+ foreach ( $folders as $placeholder ) {
+ $full_path = Imagify_Files_Scan::remove_placeholder( $placeholder );
+ $full_path = realpath( $full_path );
+
+ if ( ! $full_path || ! $filesystem->is_readable( $full_path ) || ! $filesystem->is_dir( $full_path ) ) {
+ continue;
+ }
+
+ if ( Imagify_Files_Scan::is_path_forbidden( trailingslashit( $full_path ) ) ) {
+ continue;
+ }
+
+ $folder_ids[] = $folders_db->insert( array(
+ 'path' => $placeholder,
+ 'active' => 1,
+ ) );
+ }
+
+ return array_filter( $folder_ids );
+ }
+
+ /**
+ * Remove files that are in inactive folders and are not optimized.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public static function remove_unoptimized_files_from_inactive_folders() {
+ global $wpdb;
+
+ $folders_db = Imagify_Folders_DB::get_instance();
+ $folders_key = $folders_db->get_primary_key();
+ $files_table = Imagify_Files_DB::get_instance()->get_table_name();
+ $folder_ids = $folders_db->get_active_folders_column( $folders_key );
+
+ if ( $folder_ids ) {
+ $folder_ids = Imagify_DB::prepare_values_list( $folder_ids );
+
+ $wpdb->query( "DELETE FROM $files_table WHERE folder_id NOT IN ( $folder_ids ) AND ( status != 'success' OR status IS NULL )" ); // WPCS: unprepared SQL ok.
+ } else {
+ $wpdb->query( "DELETE FROM $files_table WHERE status != 'success' OR status IS NULL" ); // WPCS: unprepared SQL ok.
+ }
+ }
+
+ /**
+ * Reassign inactive files to active folders.
+ * Example:
+ * - Consider the file "/a/b/c/d/file.png".
+ * - The folder "/a/b/c/", previously active, becomes inactive.
+ * - The folder "/a/b/", previously inactive, becomes active.
+ * - The file is reassigned to the folder "/a/b/".
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public static function reassign_inactive_files() {
+ global $wpdb;
+
+ $folders_db = Imagify_Folders_DB::get_instance();
+ $folders_table = $folders_db->get_table_name();
+ $folders_key = $folders_db->get_primary_key();
+ $folders_key_esc = esc_sql( $folders_key );
+
+ $files_db = Imagify_Files_DB::get_instance();
+ $files_table = $files_db->get_table_name();
+ $files_key = $files_db->get_primary_key();
+ $files_key_esc = esc_sql( $files_key );
+
+ // All active folders.
+ $active_folders = $wpdb->get_results( "SELECT $folders_key_esc, path FROM $folders_table WHERE active = 1;", ARRAY_A ); // WPCS: unprepared SQL ok.
+
+ if ( ! $active_folders ) {
+ return;
+ }
+
+ $active_folder_ids = array();
+ $has_site_root = false;
+
+ foreach ( $active_folders as $i => $active_folder ) {
+ $active_folders[ $i ] = $folders_db->cast_row( $active_folder );
+ $active_folder_ids[] = $active_folders[ $i ][ $folders_key ];
+
+ if ( '{{ROOT}}/' === $active_folders[ $i ]['path'] ) {
+ $has_site_root = true;
+ break;
+ }
+ }
+
+ // Files not in active folders.
+ $active_folder_ids = Imagify_DB::prepare_values_list( $active_folder_ids );
+ $inactive_files = $wpdb->get_results( "SELECT $files_key_esc, path FROM $files_table WHERE folder_id NOT IN ( $active_folder_ids )", ARRAY_A ); // WPCS: unprepared SQL ok.
+
+ if ( ! $inactive_files ) {
+ return;
+ }
+
+ $filesystem = imagify_get_filesystem();
+ $file_ids_by_folder = array();
+ $active_folders = self::sort_folders( $active_folders, true );
+
+ foreach ( $inactive_files as $inactive_file ) {
+ $inactive_file = $files_db->cast_row( $inactive_file );
+ $inactive_file['full_path'] = Imagify_Files_Scan::remove_placeholder( $inactive_file['path'] );
+
+ if ( $has_site_root ) {
+ $inactive_file['dirname'] = $filesystem->dir_path( $inactive_file['full_path'] );
+ }
+
+ foreach ( $active_folders as $active_folder ) {
+ $folder_id = $active_folder[ $folders_key ];
+
+ if ( strpos( $inactive_file['full_path'], $active_folder['full_path'] ) !== 0 ) {
+ // The file is not in this folder.
+ continue;
+ }
+
+ if ( ! isset( $file_ids_by_folder[ $folder_id ] ) ) {
+ $file_ids_by_folder[ $folder_id ] = array();
+ }
+
+ if ( '{{ROOT}}/' === $active_folder['path'] ) {
+ // For the site's root: only direct childs.
+ if ( $inactive_file['dirname'] === $active_folder['full_path'] ) {
+ // This file is in the site's root folder.
+ $file_ids_by_folder[ $folder_id ][] = $inactive_file[ $files_key ];
+ }
+ break;
+ }
+
+ // This file is not in the site's root, but still a grand-child of this folder.
+ $file_ids_by_folder[ $folder_id ][] = $inactive_file[ $files_key ];
+ break;
+ }
+ }
+
+ $file_ids_by_folder = array_filter( $file_ids_by_folder );
+
+ if ( ! $file_ids_by_folder ) {
+ return;
+ }
+
+ // Set the new folder ID.
+ foreach ( $file_ids_by_folder as $folder_id => $file_ids ) {
+ $file_ids = Imagify_DB::prepare_values_list( $file_ids );
+
+ $wpdb->query( "UPDATE $files_table SET folder_id = $folder_id WHERE $files_key_esc IN ( $file_ids )" ); // WPCS: unprepared SQL ok.
+ }
+ }
+
+ /**
+ * Remove the given folders from the DB if they are inactive and have no files.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $folder_ids An array of folder IDs.
+ * @return int Number of removed folders.
+ */
+ public static function remove_empty_inactive_folders( $folder_ids = null ) {
+ global $wpdb;
+
+ $folders_db = Imagify_Folders_DB::get_instance();
+ $folders_table = $folders_db->get_table_name();
+ $folders_key = $folders_db->get_primary_key();
+ $folders_key_esc = esc_sql( $folders_key );
+ $files_table = Imagify_Files_DB::get_instance()->get_table_name();
+
+ $folder_ids = array_filter( (array) $folder_ids );
+
+ if ( $folder_ids ) {
+ $folder_ids = $folders_db->cast_col( $folder_ids, $folders_key );
+ $folder_ids = Imagify_DB::prepare_values_list( $folder_ids );
+ $in_clause = "folders.$folders_key_esc IN ( $folder_ids )";
+ } else {
+ $in_clause = '1=1';
+ }
+
+ // Within the range of given folder IDs, filter the ones that are inactive and have no files.
+ $results = $wpdb->get_col( // WPCS: unprepared SQL ok.
+ "
+ SELECT folders.$folders_key_esc FROM $folders_table AS folders
+ LEFT JOIN $files_table AS files ON folders.$folders_key_esc = files.folder_id
+ WHERE $in_clause
+ AND folders.active != 1
+ AND files.folder_id IS NULL"
+ );
+
+ if ( ! $results ) {
+ return 0;
+ }
+
+ $results = $folders_db->cast_col( $results, $folders_key );
+ $results = Imagify_DB::prepare_values_list( $results );
+
+ // Remove inactive folders with no files.
+ $wpdb->query( "DELETE FROM $folders_table WHERE $folders_key_esc IN ( $results )" ); // WPCS: unprepared SQL ok.
+
+ return (int) $wpdb->rows_affected;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Sort folders by full path.
+ * The row "full_path" is added to each folder.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $folders An array of folders with at least a "path" row.
+ * @param bool $reverse Reverse the order.
+ * @return array
+ */
+ public static function sort_folders( $folders, $reverse = false ) {
+ if ( ! $folders ) {
+ return array();
+ }
+
+ $keyed_folders = array();
+ $keyed_paths = array();
+
+ foreach ( $folders as $folder ) {
+ $folder = (array) $folder;
+ $folder['full_path'] = Imagify_Files_Scan::remove_placeholder( $folder['path'] );
+
+ $keyed_folders[ $folder['path'] ] = $folder;
+ $keyed_paths[ $folder['path'] ] = $folder['full_path'];
+ }
+
+ natcasesort( $keyed_paths );
+
+ if ( $reverse ) {
+ $keyed_paths = array_reverse( $keyed_paths, true );
+ }
+
+ $keyed_folders = array_merge( $keyed_paths, $keyed_folders );
+
+ return array_values( $keyed_folders );
+ }
+
+ /**
+ * Remove sub-paths: if 'a/b/' and 'a/b/c/' are in the array, we keep only the "parent" 'a/b/'.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $placeholders A list of "placeholdered" paths.
+ * @return array
+ */
+ public static function remove_sub_paths( $placeholders ) {
+ sort( $placeholders );
+
+ foreach ( $placeholders as $i => $placeholder_path ) {
+ if ( '{{ROOT}}/' === $placeholder_path ) {
+ continue;
+ }
+
+ if ( ! isset( $prev_path ) ) {
+ $prev_path = strtolower( Imagify_Files_Scan::remove_placeholder( $placeholder_path ) );
+ continue;
+ }
+
+ $placeholder_path = strtolower( Imagify_Files_Scan::remove_placeholder( $placeholder_path ) );
+
+ if ( strpos( $placeholder_path, $prev_path ) === 0 ) {
+ unset( $placeholders[ $i ] );
+ } else {
+ $prev_path = $placeholder_path;
+ }
+ }
+
+ return $placeholders;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-data.php b/wp-content/plugins/imagify/inc/classes/class-imagify-data.php
new file mode 100644
index 00000000..0290fb36
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-data.php
@@ -0,0 +1,103 @@
+ 0.0,
+ 'average_size_images_per_month' => 0.0,
+ 'previous_quota_percent' => 0.0,
+ );
+
+ /**
+ * The single instance of the class.
+ *
+ * @var object
+ * @since 1.7
+ * @access protected
+ */
+ protected static $_instance;
+
+ /**
+ * Get the main Instance.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return object Main instance.
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$_instance ) ) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** SANITIZATION, VALIDATION ================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Sanitize and validate an option value. Basic casts have been made.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $key The option key.
+ * @param mixed $value The value.
+ * @param mixed $default The default value.
+ * @return mixed
+ */
+ public function sanitize_and_validate_value( $key, $value, $default ) {
+ switch ( $key ) {
+ case 'total_size_images_library':
+ case 'average_size_images_per_month':
+ if ( $value <= 0 ) {
+ // Invalid.
+ return 0.0;
+ }
+ return $value;
+
+ case 'previous_quota_percent':
+ $value = round( $value, 1 );
+ return min( max( 0, $value ), 100 );
+ }
+
+ return false;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-db.php b/wp-content/plugins/imagify/inc/classes/class-imagify-db.php
new file mode 100644
index 00000000..6a12cd06
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-db.php
@@ -0,0 +1,564 @@
+query( $query ); // WPCS: unprepared SQL ok.
+ }
+ }
+
+ /**
+ * Change an array of values into a comma separated list, ready to be used in a `IN ()` clause.
+ *
+ * @since 1.6.13
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $values An array of values.
+ * @return string A comma separated list of values.
+ */
+ public static function prepare_values_list( $values ) {
+ $values = esc_sql( (array) $values );
+ $values = array_map( array( __CLASS__, 'quote_string' ), $values );
+ return implode( ',', $values );
+ }
+
+ /**
+ * Wrap a value in quotes, unless it's an integer.
+ *
+ * @since 1.6.13
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int|string $value A value.
+ * @return int|string
+ */
+ public static function quote_string( $value ) {
+ return is_numeric( $value ) ? $value : "'" . addcslashes( $value, "'" ) . "'";
+ }
+
+ /**
+ * First half of escaping for LIKE special characters % and _ before preparing for MySQL.
+ * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security.
+ *
+ * Example Prepared Statement:
+ * $wild = '%';
+ * $find = 'only 43% of planets';
+ * $like = $wild . $wpdb->esc_like( $find ) . $wild;
+ * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like );
+ *
+ * Example Escape Chain:
+ * $sql = esc_sql( $wpdb->esc_like( $input ) );
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $text The raw text to be escaped. The input typed by the user should have no extra or deleted slashes.
+ * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare() or real_escape next.
+ */
+ public static function esc_like( $text ) {
+ global $wpdb;
+
+ if ( method_exists( $wpdb, 'esc_like' ) ) {
+ // Introduced in WP 4.0.0.
+ return $wpdb->esc_like( $text );
+ }
+
+ return addcslashes( $text, '_%\\' );
+ }
+
+ /**
+ * Get Imagify mime types, ready to be used in a `IN ()` clause.
+ *
+ * @since 1.6.13
+ * @since 1.9 Added $type parameter.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $type One of 'image', 'not-image'. Any other value will return all mime types.
+ * @return string A comma separated list of mime types.
+ */
+ public static function get_mime_types( $type = null ) {
+ static $mime_types = [];
+
+ if ( empty( $type ) ) {
+ $type = 'all';
+ }
+
+ if ( ! isset( $mime_types[ $type ] ) ) {
+ $mime_types[ $type ] = self::prepare_values_list( imagify_get_mime_types( $type ) );
+ }
+
+ return $mime_types[ $type ];
+ }
+
+ /**
+ * Get post statuses related to attachments, ready to be used in a `IN ()` clause.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string A comma separated list of post statuses.
+ */
+ public static function get_post_statuses() {
+ static $statuses;
+
+ if ( ! isset( $statuses ) ) {
+ $statuses = self::prepare_values_list( imagify_get_post_statuses() );
+ }
+
+ return $statuses;
+ }
+
+ /**
+ * Get the SQL JOIN clause to use to get only attachments that have the required WP metadata.
+ * It returns an empty string if the database has no attachments without the required metadada.
+ * It also triggers Imagify_DB::unlimit_joins().
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $id_field An ID field to match the metadata ID against in the JOIN clause.
+ * Default is the posts table `ID` field, using the `p` alias: `p.ID`.
+ * In case of "false" value or PEBKAC, fallback to the same field without alias.
+ * @param bool $matching Set to false to get a query to fetch metas NOT matching the file extensions.
+ * @param bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
+ * @return string
+ */
+ public static function get_required_wp_metadata_join_clause( $id_field = 'p.ID', $matching = true, $test = true ) {
+ global $wpdb;
+
+ if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
+ return '';
+ }
+
+ self::unlimit_joins();
+ $clause = '';
+
+ if ( ! $id_field || ! is_string( $id_field ) ) {
+ $id_field = "$wpdb->posts.ID";
+ }
+
+ $join = $matching ? 'INNER' : 'LEFT';
+
+ foreach ( self::get_required_wp_metadata_aliases() as $meta_name => $alias ) {
+ $clause .= "
+ $join JOIN $wpdb->postmeta AS $alias
+ ON ( $id_field = $alias.post_id AND $alias.meta_key = '$meta_name' )";
+ }
+
+ return $clause;
+ }
+
+ /**
+ * Get the SQL part to be used in a WHERE clause, to get only attachments that have (in)valid '_wp_attached_file' and '_wp_attachment_metadata' metadatas.
+ * It returns an empty string if the database has no attachments without the required metadada.
+ *
+ * @since 1.7
+ * @since 1.7.1.2 Use a single $arg parameter instead of 3. New $prepared parameter.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $args {
+ * Optional. An array of arguments.
+ *
+ * string $aliases The aliases to use for the meta values.
+ * bool $matching Set to false to get a query to fetch invalid metas.
+ * bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
+ * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
+ * }.
+ * @return string A query.
+ */
+ public static function get_required_wp_metadata_where_clause( $args = array() ) {
+ static $query = array();
+
+ $args = imagify_merge_intersect( $args, array(
+ 'aliases' => array(),
+ 'matching' => true,
+ 'test' => true,
+ 'prepared' => false,
+ ) );
+
+ list( $aliases, $matching, $test, $prepared ) = array_values( $args );
+
+ if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
+ return '';
+ }
+
+ if ( $aliases && is_string( $aliases ) ) {
+ $aliases = array(
+ '_wp_attached_file' => $aliases,
+ );
+ } elseif ( ! is_array( $aliases ) ) {
+ $aliases = array();
+ }
+
+ $aliases = imagify_merge_intersect( $aliases, self::get_required_wp_metadata_aliases() );
+ $key = implode( '|', $aliases ) . '|' . (int) $matching;
+
+ if ( isset( $query[ $key ] ) ) {
+ return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
+ }
+
+ unset( $args['prepared'] );
+ $alias_1 = $aliases['_wp_attached_file'];
+ $alias_2 = $aliases['_wp_attachment_metadata'];
+ $extensions = self::get_extensions_where_clause( $args );
+
+ if ( $matching ) {
+ $query[ $key ] = "AND $alias_1.meta_value NOT LIKE '%://%' AND $alias_1.meta_value NOT LIKE '_:\\\\\%' $extensions";
+ } else {
+ $query[ $key ] = "AND ( $alias_2.meta_value IS NULL OR $alias_1.meta_value IS NULL OR $alias_1.meta_value LIKE '%://%' OR $alias_1.meta_value LIKE '_:\\\\\%' $extensions )";
+ }
+
+ return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
+ }
+
+ /**
+ * Get the SQL part to be used in a WHERE clause, to get only attachments that have a valid file extensions.
+ * It returns an empty string if the database has no attachments without the required metadada.
+ *
+ * @since 1.7
+ * @since 1.7.1.2 Use a single $arg parameter instead of 3. New $prepared parameter.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $args {
+ * Optional. An array of arguments.
+ *
+ * string $alias The alias to use for the meta value.
+ * bool $matching Set to false to get a query to fetch metas NOT matching the file extensions.
+ * bool $test Test if the site has attachments without required metadata before returning the query. False to bypass the test and get the query anyway.
+ * bool $prepared Set to true if the query will be prepared with using $wpdb->prepare().
+ * }.
+ * @return string A query.
+ */
+ public static function get_extensions_where_clause( $args = false ) {
+ static $extensions;
+ static $query = array();
+
+ $args = imagify_merge_intersect( $args, array(
+ 'alias' => array(),
+ 'matching' => true,
+ 'test' => true,
+ 'prepared' => false,
+ ) );
+
+ list( $alias, $matching, $test, $prepared ) = array_values( $args );
+
+ if ( $test && ! imagify_has_attachments_without_required_metadata() ) {
+ return '';
+ }
+
+ if ( ! isset( $extensions ) ) {
+ $extensions = array_keys( imagify_get_mime_types() );
+ $extensions = implode( '|', $extensions );
+ $extensions = explode( '|', $extensions );
+ }
+
+ if ( ! $alias ) {
+ $alias = self::get_required_wp_metadata_aliases();
+ $alias = $alias['_wp_attached_file'];
+ }
+
+ $key = $alias . '|' . (int) $matching;
+
+ if ( isset( $query[ $key ] ) ) {
+ return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
+ }
+
+ if ( $matching ) {
+ $query[ $key ] = "AND ( LOWER( $alias.meta_value ) LIKE '%." . implode( "' OR LOWER( $alias.meta_value ) LIKE '%.", $extensions ) . "' )";
+ } else {
+ $query[ $key ] = "OR ( LOWER( $alias.meta_value ) NOT LIKE '%." . implode( "' AND LOWER( $alias.meta_value ) NOT LIKE '%.", $extensions ) . "' )";
+ }
+
+ return $prepared ? str_replace( '%', '%%', $query[ $key ] ) : $query[ $key ];
+ }
+
+ /**
+ * Get the aliases used for the metas in self::get_required_wp_metadata_join_clause(), self::get_required_wp_metadata_where_clause(), and self::get_extensions_where_clause().
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array An array with the meta name as key and its alias as value.
+ */
+ public static function get_required_wp_metadata_aliases() {
+ return array(
+ '_wp_attached_file' => 'imrwpmt1',
+ '_wp_attachment_metadata' => 'imrwpmt2',
+ );
+ }
+
+ /**
+ * Combine two arrays with some specific keys.
+ * We use this function to combine the result of 2 SQL queries.
+ *
+ * @since 1.6.13
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $keys An array of keys.
+ * @param array $values An array of arrays like array( 'id' => id, 'value' => value ).
+ * @param int $keep_keys_order Set to true to return an array ordered like $keys instead of $values.
+ * @return array The combined arrays.
+ */
+ public static function combine_query_results( $keys, $values, $keep_keys_order = false ) {
+ if ( ! $keys || ! $values ) {
+ return array();
+ }
+
+ $result = array();
+ $keys = array_flip( $keys );
+
+ foreach ( $values as $v ) {
+ if ( isset( $keys[ $v['id'] ] ) ) {
+ $result[ $v['id'] ] = $v['value'];
+ }
+ }
+
+ if ( $keep_keys_order ) {
+ $keys = array_intersect_key( $keys, $result );
+ return array_replace( $keys, $result );
+ }
+
+ return $result;
+ }
+
+ /**
+ * A helper to retrieve all values from one or several post metas, given a list of post IDs.
+ * The $wpdb cache is flushed to save memory.
+ *
+ * @since 1.6.13
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $metas An array of meta names like:
+ * array(
+ * 'key1' => 'meta_name_1',
+ * 'key2' => 'meta_name_2',
+ * 'key3' => 'meta_name_3',
+ * )
+ * If a key contains 'data', the results will be unserialized.
+ * @param array $ids An array of post IDs.
+ * @return array An array of arrays of results like:
+ * array(
+ * 'key1' => array( post_id_1 => 'result_1', post_id_2 => 'result_2', post_id_3 => 'result_3' ),
+ * 'key2' => array( post_id_1 => 'result_4', post_id_3 => 'result_5' ),
+ * 'key3' => array( post_id_1 => 'result_6', post_id_2 => 'result_7' ),
+ * )
+ */
+ public static function get_metas( $metas, $ids ) {
+ global $wpdb;
+
+ if ( ! $ids ) {
+ return array_fill_keys( array_keys( $metas ), array() );
+ }
+
+ $sql_ids = implode( ',', $ids );
+
+ foreach ( $metas as $result_name => $meta_name ) {
+ $metas[ $result_name ] = $wpdb->get_results( // WPCS: unprepared SQL ok.
+ "SELECT pm.post_id as id, pm.meta_value as value
+ FROM $wpdb->postmeta as pm
+ WHERE pm.meta_key = '$meta_name'
+ AND pm.post_id IN ( $sql_ids )
+ ORDER BY pm.post_id DESC",
+ ARRAY_A
+ );
+
+ $wpdb->flush();
+ $metas[ $result_name ] = self::combine_query_results( $ids, $metas[ $result_name ], true );
+
+ if ( strpos( $result_name, 'data' ) !== false ) {
+ $metas[ $result_name ] = array_map( 'maybe_unserialize', $metas[ $result_name ] );
+ }
+ }
+
+ return $metas;
+ }
+
+ /**
+ * Create/Upgrade the table in the database.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $table_name The (prefixed) table name.
+ * @param string $schema_query Query representing the table schema.
+ * @return bool True on success. False otherwise.
+ */
+ public static function create_table( $table_name, $schema_query ) {
+ global $wpdb;
+
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+
+ $wpdb->hide_errors();
+
+ $schema_query = trim( $schema_query );
+ $charset_collate = $wpdb->get_charset_collate();
+
+ dbDelta( "CREATE TABLE $table_name ($schema_query) $charset_collate;" );
+
+ return empty( $wpdb->last_error ) && self::table_exists( $table_name );
+ }
+
+ /**
+ * Tell if the given table exists.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $table_name Full name of the table (with DB prefix).
+ * @return bool
+ */
+ public static function table_exists( $table_name ) {
+ global $wpdb;
+
+ $escaped_table = self::esc_like( $table_name );
+ $result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $escaped_table ) );
+
+ return $result === $table_name;
+ }
+
+ /**
+ * Cache transients used for optimization process locks.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $context The context.
+ * @param array $media_ids The media IDs.
+ */
+ public static function cache_process_locks( $context, $media_ids ) {
+ global $wpdb;
+
+ if ( ! $context || ! $media_ids || wp_using_ext_object_cache() ) {
+ return;
+ }
+
+ // Sanitize the IDs.
+ $media_ids = array_filter( $media_ids );
+ $media_ids = array_unique( $media_ids );
+
+ if ( ! $media_ids ) {
+ return;
+ }
+
+ $context_instance = imagify_get_context( $context );
+ $context = $context_instance->get_name();
+ $process_class_name = imagify_get_optimization_process_class_name( $context );
+ $transient_name = sprintf( $process_class_name::LOCK_NAME, $context, '%' );
+ $is_network_wide = $context_instance->is_network_wide();
+
+ // Do 1 DB query per context (and cache results) before doing 1 get_transient() (2 DB queries) per media ID.
+ $prefix = $is_network_wide ? '_site_transient_' : '_transient_';
+
+ if ( $is_network_wide && is_multisite() ) {
+ $network_id = function_exists( 'get_current_network_id' ) ? get_current_network_id() : (int) $wpdb->siteid;
+ $cache_prefix = "$network_id:";
+ $notoptions_key = "$network_id:notoptions";
+ $cache_group = 'site-options';
+ $results = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT meta_key as name, meta_value as value FROM $wpdb->sitemeta WHERE ( meta_key LIKE %s OR meta_key LIKE %s ) AND site_id = %d",
+ $prefix . $transient_name,
+ $prefix . 'timeout_' . $transient_name,
+ $network_id
+ ),
+ OBJECT_K
+ ); // WPCS: unprepared SQL ok.
+ } else {
+ $cache_prefix = '';
+ $notoptions_key = 'notoptions';
+ $cache_group = 'options';
+ $results = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT option_name as name, option_value as value FROM $wpdb->options WHERE ( option_name LIKE %s OR option_name LIKE %s )",
+ $prefix . $transient_name,
+ $prefix . 'timeout_' . $transient_name
+ ),
+ OBJECT_K
+ ); // WPCS: unprepared SQL ok.
+ }
+
+ $not_exist = [];
+
+ foreach ( [ '', 'timeout_' ] as $maybe_timeout ) {
+ foreach ( $media_ids as $id ) {
+ $option_name = $prefix . $maybe_timeout . str_replace( '%', $id, $transient_name );
+
+ if ( isset( $results[ $option_name ] ) ) {
+ // Cache the value.
+ $value = $results[ $option_name ]->value;
+ $value = maybe_unserialize( $value );
+ wp_cache_set( "$cache_prefix$option_name", $value, $cache_group );
+ } else {
+ // No value.
+ $not_exist[ $option_name ] = true;
+ }
+ }
+ }
+
+ if ( ! $not_exist ) {
+ return;
+ }
+
+ // Cache the options that don't exist in the DB.
+ $notoptions = wp_cache_get( $notoptions_key, $cache_group );
+ $notoptions = is_array( $notoptions ) ? $notoptions : [];
+ $notoptions = array_merge( $notoptions, $not_exist );
+
+ wp_cache_set( $notoptions_key, $notoptions, $cache_group );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-files-db.php b/wp-content/plugins/imagify/inc/classes/class-imagify-files-db.php
new file mode 100644
index 00000000..9da11a72
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-files-db.php
@@ -0,0 +1,177 @@
+ '%d',
+ 'folder_id' => '%d',
+ 'file_date' => '%s',
+ 'path' => '%s',
+ 'hash' => '%s',
+ 'mime_type' => '%s',
+ 'modified' => '%d',
+ 'width' => '%d',
+ 'height' => '%d',
+ 'original_size' => '%d',
+ 'optimized_size' => '%d',
+ 'percent' => '%d',
+ 'optimization_level' => '%d',
+ 'status' => '%s',
+ 'error' => '%s',
+ 'data' => '%s',
+ );
+ }
+
+ /**
+ * Default column values.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_column_defaults() {
+ return array(
+ 'file_id' => 0,
+ 'folder_id' => 0,
+ 'file_date' => '0000-00-00 00:00:00',
+ 'path' => '',
+ 'hash' => '',
+ 'mime_type' => '',
+ 'modified' => 0,
+ 'width' => 0,
+ 'height' => 0,
+ 'original_size' => 0,
+ 'optimized_size' => null,
+ 'percent' => null,
+ 'optimization_level' => null,
+ 'status' => null,
+ 'error' => null,
+ 'data' => [],
+ );
+ }
+
+ /**
+ * Get the query to create the table fields.
+ *
+ * For with and height: `smallint(2) unsigned` means 65,535px max.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ protected function get_table_schema() {
+ return "
+ file_id bigint(20) unsigned NOT NULL auto_increment,
+ folder_id bigint(20) unsigned NOT NULL default 0,
+ file_date datetime NOT NULL default '0000-00-00 00:00:00',
+ path varchar(191) NOT NULL default '',
+ hash varchar(32) NOT NULL default '',
+ mime_type varchar(100) NOT NULL default '',
+ modified tinyint(1) unsigned NOT NULL default 0,
+ width smallint(2) unsigned NOT NULL default 0,
+ height smallint(2) unsigned NOT NULL default 0,
+ original_size int(4) unsigned NOT NULL default 0,
+ optimized_size int(4) unsigned default NULL,
+ percent smallint(2) unsigned default NULL,
+ optimization_level tinyint(1) unsigned default NULL,
+ status varchar(20) default NULL,
+ error varchar(255) default NULL,
+ data longtext default NULL,
+ PRIMARY KEY (file_id),
+ UNIQUE KEY path (path),
+ KEY folder_id (folder_id),
+ KEY optimization_level (optimization_level),
+ KEY status (status),
+ KEY modified (modified)";
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-files-iterator.php b/wp-content/plugins/imagify/inc/classes/class-imagify-files-iterator.php
new file mode 100644
index 00000000..e6904bd8
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-files-iterator.php
@@ -0,0 +1,117 @@
+include_folders = (bool) $include_folders;
+ $this->filesystem = Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Check whether the current element of the iterator is acceptable.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool Returns whether the current element of the iterator is acceptable through this filter.
+ */
+ public function accept() {
+ static $extensions, $has_extension_method;
+
+ $file_path = $this->current()->getPathname();
+
+ // Prevent triggering an open_basedir restriction error.
+ $file_name = $this->filesystem->file_name( $file_path );
+
+ if ( '.' === $file_name || '..' === $file_name ) {
+ return false;
+ }
+
+ // Forbidden file/folder paths and names.
+ $is_dir = $this->isDir();
+
+ if ( $is_dir ) {
+ $file_path = trailingslashit( $file_path );
+ }
+
+ if ( Imagify_Files_Scan::is_path_forbidden( $file_path ) ) {
+ return false;
+ }
+
+ // OK for folders.
+ if ( $this->include_folders && $is_dir ) {
+ return true;
+ }
+
+ // Only files.
+ if ( ! $this->current()->isFile() ) {
+ return false;
+ }
+
+ // Only files with the required extension.
+ if ( ! isset( $extensions ) ) {
+ $extensions = array_keys( imagify_get_mime_types() );
+ $extensions = implode( '|', $extensions );
+ }
+
+ if ( ! isset( $has_extension_method ) ) {
+ // This method was introduced in php 5.3.6.
+ $has_extension_method = method_exists( $this->current(), 'getExtension' );
+ }
+
+ if ( $has_extension_method ) {
+ $file_extension = strtolower( $this->current()->getExtension() );
+ } else {
+ $file_extension = strtolower( $this->filesystem->path_info( $file_path, 'extension' ) );
+ }
+
+ return preg_match( '@^' . $extensions . '$@', $file_extension );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-files-list-table.php b/wp-content/plugins/imagify/inc/classes/class-imagify-files-list-table.php
new file mode 100644
index 00000000..9539307b
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-files-list-table.php
@@ -0,0 +1,1109 @@
+ 'imagify-files',
+ 'screen' => isset( $args['screen'] ) ? convert_to_screen( $args['screen'] ) : null,
+ ) );
+
+ $this->modes = array(
+ 'list' => __( 'List View', 'imagify' ),
+ );
+
+ $this->filesystem = Imagify_Filesystem::get_instance();
+ $this->views = Imagify_Views::get_instance();
+ }
+
+ /**
+ * Prepares the list of items for displaying.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function prepare_items() {
+ global $wpdb;
+
+ add_screen_option( 'per_page', array(
+ 'label' => __( 'Number of files per page', 'imagify' ),
+ 'default' => 20,
+ 'option' => self::PER_PAGE_OPTION,
+ ) );
+
+ $files_db = Imagify_Files_DB::get_instance();
+ $files_table = $files_db->get_table_name();
+ $files_key = $files_db->get_primary_key();
+ $files_key_esc = esc_sql( $files_key );
+ $per_page = $this->get_items_per_page( self::PER_PAGE_OPTION );
+
+ // Prepare the query to get items.
+ $page = $this->get_pagenum();
+ $offset = ( $page - 1 ) * $per_page;
+ $orderbys = $this->get_sortable_columns();
+ $orderby = 'path';
+ $order = 'ASC';
+ $folders = array();
+ $file_ids = array();
+ $where = '';
+
+ $sent_orderby = filter_input( INPUT_GET, 'orderby', FILTER_SANITIZE_STRING );
+ $sent_order = filter_input( INPUT_GET, 'order', FILTER_SANITIZE_STRING );
+ $folder_filter = self::get_folder_filter();
+ $status_filter = self::get_status_filter();
+
+ if ( ! empty( $sent_orderby ) && isset( $orderbys[ $sent_orderby ] ) ) {
+ $orderby = $sent_orderby;
+ $order = is_array( $orderbys[ $orderby ] ) ? 'DESC' : 'ASC';
+
+ if ( 'optimization' === $orderby ) {
+ $orderby = 'percent';
+ }
+ }
+
+ if ( $sent_order ) {
+ $order = 'ASC' === strtoupper( $sent_order ) ? 'ASC' : 'DESC';
+ }
+
+ if ( $folder_filter ) {
+ // Display only files from a specific custom folder.
+ $where = "WHERE folder_id = $folder_filter";
+ }
+
+ if ( $status_filter ) {
+ // Display files optimized, not optimized, or with error.
+ $where .= $where ? ' AND ' : 'WHERE ';
+
+ switch ( $status_filter ) {
+ case 'optimized':
+ $where .= "( status = 'success' OR status = 'already_optimized' )";
+ break;
+ case 'unoptimized':
+ $where .= 'status IS NULL';
+ break;
+ case 'errors':
+ $where .= "status = 'error'";
+ break;
+ }
+ }
+
+ // Pagination.
+ $this->set_pagination_args( array(
+ 'total_items' => (int) $wpdb->get_var( "SELECT COUNT($files_key_esc) FROM $files_table $where" ), // WPCS: unprepared SQL ok.
+ 'per_page' => $per_page,
+ ) );
+
+ // Get items.
+ $this->items = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $files_table $where ORDER BY $orderby $order LIMIT %d, %d", $offset, $per_page ) ); // WPCS: unprepared SQL ok.
+
+ if ( ! $this->items ) {
+ return;
+ }
+
+ // Prepare items.
+ foreach ( $this->items as $i => $item ) {
+ // Cast values.
+ $item = $files_db->cast_row( $item );
+
+ // Store the folders used by the items to get their data later in 1 query.
+ $folders[ $item->folder_id ] = $item->folder_id;
+
+ // Store the item IDs to store transients later in 1 query.
+ $file_ids[ $item->$files_key ] = $item->$files_key;
+
+ // Use Imagify objects + add related folder ID and path (set later).
+ $this->items[ $i ] = (object) [
+ 'process' => imagify_get_optimization_process( $item, 'custom-folders' ),
+ 'folder_id' => $item->folder_id,
+ 'folder_path' => false,
+ 'is_folder_active' => true,
+ ];
+
+ if ( ! $this->items[ $i ]->process->is_valid() ) {
+ unset( $this->items[ $i ] );
+ }
+ }
+
+ $folders = array_filter( $folders );
+
+ // Cache transient values.
+ Imagify_DB::cache_process_locks( 'custom-folders', $file_ids );
+
+ if ( ! $folders ) {
+ return;
+ }
+
+ // Get folders data.
+ $folders_db = Imagify_Folders_DB::get_instance();
+ $folders_table = $folders_db->get_table_name();
+ $folders_key_esc = esc_sql( $folders_db->get_primary_key() );
+ $folders = Imagify_DB::prepare_values_list( $folders );
+ $folders = $wpdb->get_results( "SELECT * FROM $folders_table WHERE $folders_key_esc IN ( $folders )" ); // WPCS: unprepared SQL ok.
+
+ if ( ! $folders ) {
+ return;
+ }
+
+ // Cast folders data and store data into a property.
+ foreach ( $folders as $folder ) {
+ $folder = $folders_db->cast_row( $folder );
+
+ $this->folders[ $folder->folder_id ] = $folder;
+ }
+
+ // Set folders path to each item.
+ foreach ( $this->items as $i => $item ) {
+ if ( $item->folder_id && isset( $this->folders[ $item->folder_id ] ) ) {
+ $item->folder_path = $this->folders[ $item->folder_id ]->path;
+ $item->is_folder_active = (bool) $this->folders[ $item->folder_id ]->active;
+ }
+ }
+
+ // Button templates.
+ Imagify_Views::get_instance()->print_js_template_in_footer( 'button/processing' );
+ }
+
+ /**
+ * Message to be displayed when there are no items.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function no_items() {
+ if ( self::get_status_filter() ) {
+ // Filter by status.
+ switch ( self::get_status_filter() ) {
+ case 'optimized':
+ /* translators: 1 is a link tag start, 2 is the link tag end. */
+ printf( esc_html__( 'No optimized files. Have you tried the %1$sbulk optimization%2$s yet?', 'imagify' ), '', ' ' );
+ return;
+
+ case 'unoptimized':
+ esc_html_e( 'No unoptimized files, hurray!', 'imagify' );
+ return;
+
+ case 'errors':
+ esc_html_e( 'No errors, hurray!', 'imagify' );
+ return;
+ }
+ }
+
+ $args = array(
+ 'action' => 'imagify_scan_custom_folders',
+ '_wpnonce' => wp_create_nonce( 'imagify_scan_custom_folders' ),
+ '_wp_http_referer' => get_imagify_admin_url( 'files-list' ),
+ );
+
+ if ( self::get_folder_filter() ) {
+ // A specific custom folder (selected or not).
+ $args['folder'] = self::get_folder_filter();
+ $args['_wp_http_referer'] = rawurlencode( add_query_arg( 'folder-filter', self::get_folder_filter(), $args['_wp_http_referer'] ) );
+
+ printf(
+ /* translators: 1 and 2 are link tag starts, 3 is a link tag end. */
+ esc_html__( 'No files yet. Do you want to %1$sscan this folder%3$s for new files or launch a %2$sbulk optimization%3$s directly?', 'imagify' ),
+ '',
+ ' ',
+ ' '
+ );
+ return;
+ }
+
+ if ( Imagify_Folders_DB::get_instance()->has_active_folders() ) {
+ // All selected custom folders.
+ $args['_wp_http_referer'] = rawurlencode( $args['_wp_http_referer'] );
+ printf(
+ /* translators: 1 and 2 are link tag starts, 3 is a link tag end. */
+ esc_html__( 'No files yet. Do you want to %1$sscan your selected folders%3$s for new files or launch a %2$sbulk optimization%3$s directly?', 'imagify' ),
+ '',
+ ' ',
+ ' '
+ );
+ return;
+ }
+
+ // Nothing selected in the settings.
+ printf(
+ /* translators: 1 is a link tag start, 2 is the link tag end. */
+ esc_html__( 'To see things appear here, you must select folders in the settings page first :)', 'imagify' ),
+ '',
+ ' '
+ );
+ }
+
+ /**
+ * Display views.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function views() {
+ global $wpdb;
+
+ // Get all folders.
+ $folders_table = Imagify_Folders_DB::get_instance()->get_table_name();
+ $folders = $wpdb->get_results( "SELECT folder_id, path FROM $folders_table" ); // WPCS: unprepared SQL ok.
+
+ if ( ! $folders ) {
+ return;
+ }
+
+ $files_db = Imagify_Files_DB::get_instance();
+ $files_table = $files_db->get_table_name();
+ $files_key_esc = esc_sql( $files_db->get_primary_key() );
+
+ // Filter files by folder.
+ $folder_filters = array();
+ $root_id = 0;
+ $counts = $wpdb->get_results( "SELECT folder_id, COUNT( $files_key_esc ) AS count FROM $files_table GROUP BY folder_id", OBJECT_K ); // WPCS: unprepared SQL ok.
+
+ foreach ( $folders as $folder ) {
+ if ( '{{ROOT}}/' === $folder->path ) {
+ $root_id = $folder->folder_id;
+ $folder_filters[ $folder->folder_id ] = '/';
+ } else {
+ $folder_filters[ $folder->folder_id ] = '/' . trim( $this->filesystem->make_path_relative( Imagify_Files_Scan::remove_placeholder( $folder->path ) ), '/' );
+ }
+ }
+
+ natcasesort( $folder_filters );
+
+ if ( $root_id ) {
+ $folder_filters[ $root_id ] = __( 'Site\'s root', 'imagify' );
+ }
+
+ foreach ( $folder_filters as $folder_id => $label ) {
+ $folder_filters[ $folder_id ] .= ' (' . ( isset( $counts[ $folder_id ] ) ? (int) $counts[ $folder_id ]->count : 0 ) . ')';
+ }
+
+ // Filter files by status.
+ $counts = $wpdb->get_results( "SELECT status, COUNT( $files_key_esc ) AS count FROM $files_table GROUP BY status", OBJECT_K ); // WPCS: unprepared SQL ok.
+ $status_filters = array(
+ 'optimized' => 0,
+ 'unoptimized' => 0,
+ 'errors' => 0,
+ );
+
+ if ( isset( $counts['success'] ) ) {
+ $status_filters['optimized'] += $counts['success']->count;
+ }
+
+ if ( isset( $counts['already_optimized'] ) ) {
+ $status_filters['optimized'] += $counts['already_optimized']->count;
+ }
+
+ if ( isset( $counts[''] ) ) {
+ $status_filters['unoptimized'] += $counts['']->count;
+ }
+
+ if ( isset( $counts['error'] ) ) {
+ $status_filters['errors'] += $counts['error']->count;
+ }
+
+ $status_filters = array(
+ '' => __( 'All Media Files', 'imagify' ),
+ 'optimized' => _x( 'Optimized', 'Media Files', 'imagify' ) . ' (' . $status_filters['optimized'] . ')',
+ 'unoptimized' => _x( 'Unoptimized', 'Media Files', 'imagify' ) . ' (' . $status_filters['unoptimized'] . ')',
+ 'errors' => _x( 'Errors', 'Media Files', 'imagify' ) . ' (' . $status_filters['errors'] . ')',
+ );
+
+ // Get submitted values.
+ $folder_filter = self::get_folder_filter();
+ $status_filter = self::get_status_filter();
+
+ // Display the filters.
+ if ( method_exists( $this->screen, 'render_screen_reader_content' ) ) {
+ // Introduced in WP 4.4.
+ $this->screen->render_screen_reader_content( 'heading_views' );
+ }
+ ?>
+
+
+
+
+
+ %s', '', selected( $folder_filter, 0, false ), esc_html__( 'All Folders', 'imagify' ) );
+
+ foreach ( $folder_filters as $folder_id => $label ) {
+ printf( '%s ', $folder_id, selected( $folder_filter, $folder_id, false ), esc_html( $label ) );
+ }
+ ?>
+
+
+
+
+ $label ) {
+ printf( '%s ', $status, selected( $status_filter, $status, false ), esc_html( $label ) );
+ }
+ ?>
+
+
+ 'folders-query-submit' ) ); ?>
+
+ extra_tablenav( 'bar' ); ?>
+
+
+ option_title ) with the list of bulk actions available on this table.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_bulk_actions() {
+ return array(
+ 'imagify-bulk-refresh-status' => __( 'Refresh status', 'imagify' ),
+ );
+ }
+
+ /**
+ * Get a list of columns. The format is:
+ * 'internal-name' => 'Title'
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_columns() {
+ return array(
+ 'cb' => ' ',
+ 'title' => __( 'File', 'imagify' ),
+ 'folder' => __( 'Folder', 'imagify' ),
+ 'optimization' => __( 'Optimization', 'imagify' ),
+ 'status' => __( 'Status', 'imagify' ),
+ 'optimization_level' => __( 'Optimization Level', 'imagify' ),
+ 'actions' => __( 'Actions', 'imagify' ),
+ );
+ }
+
+ /**
+ * Get a list of sortable columns. The format is:
+ * 'internal-name' => 'orderby'
+ * or
+ * 'internal-name' => array( 'orderby', true )
+ *
+ * The second format will make the initial sorting order be descending.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_sortable_columns() {
+ return array(
+ 'folder' => 'folder',
+ 'optimization' => array( 'optimization', true ),
+ 'status' => 'status',
+ 'optimization_level' => array( 'optimization_level', true ),
+ );
+ }
+
+ /**
+ * Get a column contents.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column The column "name": "cb", "title", "optimization_level", etc.
+ * @param object $item The current item. It must contain at least a $process property.
+ * @return string HTML contents,
+ */
+ public function get_column( $column, $item ) {
+ if ( ! method_exists( $this, 'column_' . $column ) ) {
+ return '';
+ }
+
+ ob_start();
+ call_user_func( array( $this, 'column_' . $column ), $item );
+ return ob_get_clean();
+ }
+
+ /**
+ * Handles the checkbox column output.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ public function column_cb( $item ) {
+ $media_id = $item->process->get_media()->get_id();
+ ?>
+
+
+ maybe_set_item_folder( $item );
+ $media = $item->process->get_media();
+ $url = $media->get_fullsize_url();
+ $base = ! empty( $item->folder_path ) ? Imagify_Files_Scan::remove_placeholder( $item->folder_path ) : '';
+ $title = $this->filesystem->make_path_relative( $media->get_fullsize_path(), $base );
+
+ list( $mime ) = explode( '/', $media->get_mime_type() );
+
+ if ( $media->is_image() ) {
+ $dimensions = $media->get_dimensions();
+ $orientation = $dimensions['width'] > $dimensions['height'] ? ' landscape' : ' portrait';
+ $orientation = $dimensions['width'] && $dimensions['height'] ? $orientation : '';
+
+ if ( ! wp_doing_ajax() && $item->process->get_data()->get_optimized_size( false, 0, false ) > 100000 ) {
+ // LazyLoad.
+ $image_tag = ' ';
+ $image_tag .= ' ';
+ } else {
+ $image_tag = ' ';
+ }
+ } else {
+ $orientation = '';
+ $image_tag = ' ';
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+ comparison_tool_button( $item ); ?>
+
+ maybe_set_item_folder( $item );
+
+ if ( empty( $item->folder_path ) ) {
+ return;
+ }
+
+ $format = '%s';
+ $filter = self::get_folder_filter();
+
+ if ( $filter !== $item->folder_id ) {
+ $format = '%s ';
+ }
+
+ if ( '{{ROOT}}/' === $item->folder_path ) {
+ // It's the site's root.
+ printf( $format, __( 'Site\'s root', 'imagify' ) );
+ } else {
+ printf( $format, '/' . trim( $this->filesystem->make_path_relative( Imagify_Files_Scan::remove_placeholder( $item->folder_path ) ), '/' ) . '' );
+ }
+
+ if ( ! $item->is_folder_active ) {
+ echo ' ';
+ _e( 'This folder is not selected for bulk optimization.', 'imagify' );
+ }
+ }
+
+ /**
+ * Handles the optimization data column output.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ public function column_optimization( $item ) {
+ $data = $item->process->get_data();
+ $media_id = $item->process->get_media()->get_id();
+ ?>
+
+
+
+ get_original_size() ); ?>
+
+ is_optimized() ) { ?>
+
+
+ get_optimized_size() ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ get_saving_percent(); ?> %
+
+
+ process->get_media()->is_image() ) {
+ $has_webp = $item->process->has_webp() ? __( 'Yes', 'imagify' ) : __( 'No', 'imagify' );
+ ?>
+
+
+
+
+
+
+ process->get_data();
+ $row = $data->get_row();
+ $status = $data->get_optimization_status();
+ $messages = [];
+
+ if ( ! $status ) {
+ // File is not optimized.
+ $messages[] = '' . esc_html_x( 'Not optimized', 'Media File', 'imagify' ) . ' ';
+ } elseif ( ! empty( $row['error'] ) ) {
+ // Error or already optimized.
+ $messages[] = '' . esc_html( imagify_translate_api_message( $row['error'] ) ) . ' ';
+ }
+
+ if ( empty( $row['modified'] ) && ! $messages ) {
+ // No need to display this if we already have another message to display.
+ $messages[] = '' . esc_html__( 'No changes found', 'imagify' ) . ' ';
+ } elseif ( ! empty( $row['modified'] ) ) {
+ // The file has changed or is missing.
+ $messages[] = '' . esc_html__( 'The file has changed', 'imagify' ) . ' ';
+ }
+
+ echo implode( ' ', $messages );
+
+ $this->refresh_status_button( $item );
+ }
+
+ /**
+ * Handles the optimization level column output.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ public function column_optimization_level( $item ) {
+ $data = $item->process->get_data();
+
+ if ( ! $data->is_error() ) {
+ echo imagify_get_optimization_level_label( $data->get_optimization_level(), '%ICON% %s' );
+ }
+ }
+
+ /**
+ * Handles the actions column output.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ public function column_actions( $item ) {
+ static $done = false;
+
+ if ( ! Imagify_Requirements::is_api_key_valid() ) {
+ // Stop the process if the API key isn't valid.
+ if ( ! $done ) {
+ // No need to display this on every row.
+ $done = true;
+ esc_html_e( 'Invalid API key', 'imagify' );
+ echo '' . __( 'Check your Settings', 'imagify' ) . ' ';
+ }
+ return;
+ }
+
+ if ( $item->process->is_locked() ) {
+ Imagify_Views::get_instance()->print_template( 'button-processing', [
+ 'label' => __( 'Optimizing...', 'imagify' ),
+ ] );
+ return;
+ }
+
+ $this->optimize_button( $item );
+ $this->retry_button( $item );
+ $this->reoptimize_buttons( $item );
+ $this->generate_webp_versions_button( $item );
+ $this->delete_webp_versions_button( $item );
+ $this->restore_button( $item );
+ }
+
+ /**
+ * Prints a button to optimize the file.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ protected function optimize_button( $item ) {
+ if ( $item->process->get_data()->get_optimization_status() ) {
+ // Already optimized.
+ return;
+ }
+
+ $media = $item->process->get_media();
+ $class = $media->has_backup() ? ' file-has-backup' : '';
+ $url = get_imagify_admin_url( 'optimize-file', [
+ 'attachment_id' => $media->get_id(),
+ ] );
+
+ echo $this->views->get_template( 'button/optimize', [
+ 'url' => $url,
+ 'atts' => [
+ 'class' => 'button-primary button-imagify-optimize' . $class,
+ ],
+ ] );
+ }
+
+ /**
+ * Prints a button to retry to optimize the file.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ protected function retry_button( $item ) {
+ $data = $item->process->get_data();
+
+ if ( ! $data->is_already_optimized() && ! $data->is_error() ) {
+ // Not optimized or successfully optimized.
+ return;
+ }
+
+ $media = $item->process->get_media();
+ $class = $media->has_backup() ? ' file-has-backup' : '';
+ $url = get_imagify_admin_url( 'optimize-file', [
+ 'attachment_id' => $media->get_id(),
+ ] );
+
+ echo $this->views->get_template( 'button/retry-optimize', [
+ 'url' => $url,
+ 'atts' => [
+ 'class' => 'button button-imagify-optimize' . $class,
+ ],
+ ] );
+ echo ' ';
+ }
+
+ /**
+ * Prints buttons to re-optimize the file to other levels.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ protected function reoptimize_buttons( $item ) {
+ $data = $item->process->get_data();
+
+ if ( ! $data->get_optimization_status() ) {
+ // Not optimized yet.
+ return;
+ }
+
+ $is_already_optimized = $data->is_already_optimized();
+ $media = $item->process->get_media();
+ $can_reoptimize = $is_already_optimized || $media->has_backup();
+
+ // Don't display anything if there is no backup or the image has been optimized.
+ if ( ! $can_reoptimize ) {
+ return;
+ }
+
+ $media_level = $data->get_optimization_level();
+ $data = [];
+ $url_args = [ 'attachment_id' => $media->get_id() ];
+
+ foreach ( [ 2, 1, 0 ] as $level ) {
+ /**
+ * Display a link if:
+ * - the level is lower than the one used to optimize the media,
+ * - or, the level is higher and the media is not already optimized.
+ */
+ if ( $media_level < $level || ( $media_level > $level && ! $is_already_optimized ) ) {
+ $url_args['optimization_level'] = $level;
+ $data['optimization_level'] = $level;
+ $data['url'] = get_imagify_admin_url( 'reoptimize-file', $url_args );
+
+ echo $this->views->get_template( 'button/re-optimize', $data );
+ echo ' ';
+ }
+ }
+ }
+
+ /**
+ * Prints a button to generate webp versions if they are missing.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ protected function generate_webp_versions_button( $item ) {
+ $button = get_imagify_attachment_generate_webp_versions_link( $item->process );
+
+ if ( $button ) {
+ echo $button . ' ';
+ }
+ }
+
+ /**
+ * Prints a button to delete webp versions when the status is "already_optimized".
+ *
+ * @since 1.9.6
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ protected function delete_webp_versions_button( $item ) {
+ $button = get_imagify_attachment_delete_webp_versions_link( $item->process );
+
+ if ( $button ) {
+ echo $button . ' ';
+ }
+ }
+
+ /**
+ * Prints a button to restore the file.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ protected function restore_button( $item ) {
+ $data = $item->process->get_data();
+ $media = $item->process->get_media();
+
+ if ( ! $data->is_optimized() || ! $media->has_backup() ) {
+ return;
+ }
+
+ $url = get_imagify_admin_url( 'restore-file', array(
+ 'attachment_id' => $media->get_id(),
+ ) );
+
+ echo $this->views->get_template( 'button/restore', [ 'url' => $url ] );
+ }
+
+ /**
+ * Prints a button to check if the file has been modified or not.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ protected function refresh_status_button( $item ) {
+ $url = get_imagify_admin_url( 'refresh-file-modified', array(
+ 'attachment_id' => $item->process->get_media()->get_id(),
+ ) );
+
+ echo ' ';
+ echo $this->views->get_template( 'button/refresh-status', [ 'url' => $url ] );
+ }
+
+ /**
+ * Prints a button for the comparison tool (before / after optimization).
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param object $item The current item. It must contain at least a $process property.
+ */
+ protected function comparison_tool_button( $item ) {
+ $data = $item->process->get_data();
+ $media = $item->process->get_media();
+
+ if ( ! $data->is_optimized() || ! $media->has_backup() || ! $media->is_image() ) {
+ return;
+ }
+
+ $file_path = $media->get_fullsize_path();
+
+ if ( ! $file_path ) {
+ return;
+ }
+
+ $dimensions = $media->get_dimensions();
+
+ if ( $dimensions['width'] < 360 ) {
+ return;
+ }
+
+ $backup_url = $media->get_backup_url();
+
+ echo $this->views->get_template( 'button/compare-images', [
+ 'url' => $backup_url,
+ 'backup_url' => $backup_url,
+ 'original_url' => $media->get_fullsize_url(),
+ 'media_id' => $media->get_id(),
+ 'width' => $dimensions['width'],
+ 'height' => $dimensions['height'],
+ ] );
+
+ if ( wp_doing_ajax() ) {
+ ?>
+
+ folder_path ) ) {
+ return $item;
+ }
+
+ $item->folder_id = 0;
+ $item->folder_path = false;
+
+ $row = $item->process->get_data()->get_row();
+
+ if ( empty( $row['folder_id'] ) ) {
+ return $item;
+ }
+
+ $folder = Imagify_Folders_DB::get_instance()->get( $row['folder_id'] );
+
+ if ( ! $folder ) {
+ return $item;
+ }
+
+ $item->folder_id = $folder['folder_id'];
+ $item->folder_path = $folder['path'];
+ $item->is_folder_active = (bool) $folder['active'];
+
+ return $item;
+ }
+
+ /**
+ * Get the name of the default primary column.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string Name of the default primary column, in this case, 'title'.
+ */
+ protected function get_default_primary_column_name() {
+ return 'title';
+ }
+
+ /**
+ * Get a list of CSS classes for the WP_List_Table table tag.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return array List of CSS classes for the table tag.
+ */
+ protected function get_table_classes() {
+ return array( 'widefat', 'fixed', 'striped', 'media', $this->_args['plural'] );
+ }
+
+ /**
+ * Allow to save the screen options when submitted by the user.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool|int $status Screen option value. Default false to skip.
+ * @param string $option The option name.
+ * @param int $value The number of rows to use.
+ * @return int|bool
+ */
+ public static function save_screen_options( $status, $option, $value ) {
+ if ( self::PER_PAGE_OPTION === $option ) {
+ return (int) $value;
+ }
+
+ return $status;
+ }
+
+ /**
+ * Get the requested folder filter.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public static function get_folder_filter() {
+ static $filter;
+
+ if ( ! isset( $filter ) ) {
+ $filter = filter_input( INPUT_GET, 'folder-filter', FILTER_VALIDATE_INT );
+ $filter = max( 0, $filter );
+ }
+
+ return $filter;
+ }
+
+ /**
+ * Get the requested status filter.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public static function get_status_filter() {
+ static $filter;
+
+ if ( isset( $filter ) ) {
+ return $filter;
+ }
+
+ $values = array(
+ 'optimized' => 1,
+ 'unoptimized' => 1,
+ 'errors' => 1,
+ );
+ $filter = trim( filter_input( INPUT_GET, 'status-filter', FILTER_SANITIZE_STRING ) );
+ $filter = isset( $values[ $filter ] ) ? $filter : '';
+
+ return $filter;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-files-recursive-iterator.php b/wp-content/plugins/imagify/inc/classes/class-imagify-files-recursive-iterator.php
new file mode 100644
index 00000000..e1375a7a
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-files-recursive-iterator.php
@@ -0,0 +1,104 @@
+filesystem = Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Check whether the current element of the iterator is acceptable.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool Returns whether the current element of the iterator is acceptable through this filter.
+ */
+ public function accept() {
+ static $extensions, $has_extension_method;
+
+ $file_path = $this->current()->getPathname();
+
+ // Prevent triggering an open_basedir restriction error.
+ $file_name = $this->filesystem->file_name( $file_path );
+
+ if ( '.' === $file_name || '..' === $file_name ) {
+ return false;
+ }
+
+ if ( $this->current()->isDir() ) {
+ $file_path = trailingslashit( $file_path );
+ }
+
+ if ( Imagify_Files_Scan::is_path_forbidden( $file_path ) ) {
+ return false;
+ }
+
+ // OK for folders.
+ if ( $this->hasChildren() ) {
+ return true;
+ }
+
+ // Only files.
+ if ( ! $this->current()->isFile() ) {
+ return false;
+ }
+
+ // Only files with the required extension.
+ if ( ! isset( $extensions ) ) {
+ $extensions = array_keys( imagify_get_mime_types() );
+ $extensions = implode( '|', $extensions );
+ }
+
+ if ( ! isset( $has_extension_method ) ) {
+ // This method was introduced in php 5.3.6.
+ $has_extension_method = method_exists( $this->current(), 'getExtension' );
+ }
+
+ if ( $has_extension_method ) {
+ $file_extension = strtolower( $this->current()->getExtension() );
+ } else {
+ $file_extension = strtolower( $this->filesystem->path_info( $file_path, 'extension' ) );
+ }
+
+ return preg_match( '@^' . $extensions . '$@', $file_extension );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-files-scan.php b/wp-content/plugins/imagify/inc/classes/class-imagify-files-scan.php
new file mode 100644
index 00000000..be8487b1
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-files-scan.php
@@ -0,0 +1,717 @@
+is_dir( $folder ) ) {
+ return new WP_Error( 'not_a_folder', __( 'This file is not a folder.', 'imagify' ) );
+ }
+
+ if ( self::is_path_forbidden( trailingslashit( $folder ) ) ) {
+ return new WP_Error( 'folder_forbidden', __( 'This folder is not allowed.', 'imagify' ) );
+ }
+
+ // Finally we made all our validations.
+ if ( $filesystem->is_site_root( $folder ) ) {
+ // For the site's root, we don't look in sub-folders.
+ $dir = new DirectoryIterator( $folder );
+ $dir = new Imagify_Files_Iterator( $dir, false );
+ $images = array();
+
+ foreach ( new IteratorIterator( $dir ) as $file ) {
+ $images[] = $file->getPathname();
+ }
+
+ return $images;
+ }
+
+ /**
+ * 4096 stands for FilesystemIterator::SKIP_DOTS, which was introduced in php 5.3.0.
+ * 8192 stands for FilesystemIterator::UNIX_PATHS, which was introduced in php 5.3.0.
+ */
+ $dir = new RecursiveDirectoryIterator( $folder, 4096 | 8192 );
+ $dir = new Imagify_Files_Recursive_Iterator( $dir );
+ $images = new RecursiveIteratorIterator( $dir );
+ $images = array_keys( iterator_to_array( $images ) );
+
+ return $images;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** FORBIDDEN FOLDERS AND FILES ============================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if a path is autorized.
+ * When testing a folder, the path MUST have a trailing slash.
+ *
+ * @since 1.7.1
+ * @since 1.8 The path must have a trailing slash if for a folder.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path A file or folder absolute path.
+ * @return bool
+ */
+ public static function is_path_autorized( $file_path ) {
+ return ! self::is_path_forbidden( $file_path );
+ }
+
+ /**
+ * Tell if a path is forbidden.
+ * When testing a folder, the path MUST have a trailing slash.
+ *
+ * @since 1.7
+ * @since 1.8 The path must have a trailing slash if for a folder.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path A file or folder absolute path.
+ * @return bool
+ */
+ public static function is_path_forbidden( $file_path ) {
+ static $folders;
+
+ $filesystem = imagify_get_filesystem();
+
+ if ( self::is_filename_forbidden( $filesystem->file_name( $file_path ) ) ) {
+ return true;
+ }
+
+ if ( $filesystem->is_symlinked( $file_path ) ) {
+ // Files outside the site's folder are forbidden.
+ return true;
+ }
+
+ if ( ! isset( $folders ) ) {
+ $folders = self::get_forbidden_folders();
+ $folders = array_map( 'strtolower', $folders );
+ $folders = array_flip( $folders );
+ }
+
+ $file_path = self::normalize_path_for_comparison( $file_path );
+
+ if ( isset( $folders[ $file_path ] ) ) {
+ return true;
+ }
+
+ $delim = Imagify_Filesystem::PATTERN_DELIMITER;
+
+ foreach ( self::get_forbidden_folder_patterns() as $pattern ) {
+ if ( preg_match( $delim . '^' . $pattern . $delim, $file_path ) ) {
+ return true;
+ }
+ }
+
+ foreach ( $folders as $folder => $i ) {
+ if ( strpos( $file_path, $folder ) === 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the list of folders where Imagify won't look for files to optimize.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array A list of absolute paths.
+ */
+ public static function get_forbidden_folders() {
+ static $folders;
+
+ if ( isset( $folders ) ) {
+ return $folders;
+ }
+
+ $filesystem = imagify_get_filesystem();
+ $site_root = $filesystem->get_site_root();
+ $abspath = $filesystem->get_abspath();
+ $folders = array(
+ // Server.
+ $site_root . 'cgi-bin', // `cgi-bin`
+ // WordPress.
+ $abspath . 'wp-admin', // `wp-admin`
+ $abspath . WPINC, // `wp-includes`
+ WP_CONTENT_DIR . '/mu-plugins', // MU plugins.
+ WP_CONTENT_DIR . '/upgrade', // Upgrade.
+ // Plugins.
+ WP_CONTENT_DIR . '/bps-backup', // BulletProof Security.
+ self::get_ewww_tools_path(), // EWWW: /wp-content/ewww.
+ WP_CONTENT_DIR . '/ngg', // NextGen Gallery.
+ WP_CONTENT_DIR . '/ngg_styles', // NextGen Gallery.
+ WP_CONTENT_DIR . '/w3tc-config', // W3 Total Cache.
+ WP_CONTENT_DIR . '/wfcache', // WP Fastest Cache.
+ WP_CONTENT_DIR . '/wp-rocket-config', // WP Rocket.
+ Imagify_Custom_Folders::get_backup_dir_path(), // Imagify "Custom folders" backup: /imagify-backup.
+ IMAGIFY_PATH, // Imagify plugin: /wp-content/plugins/imagify.
+ self::get_shortpixel_path(), // ShortPixel: /wp-content/uploads/ShortpixelBackups.
+ );
+
+ if ( ! is_multisite() ) {
+ $uploads_dir = $filesystem->get_upload_basedir( true );
+ $ngg_galleries = self::get_ngg_galleries_path();
+
+ if ( $ngg_galleries ) {
+ $folders[] = $ngg_galleries; // NextGen Gallery: /wp-content/gallery.
+ }
+
+ $folders[] = $uploads_dir . 'formidable'; // Formidable Forms: /wp-content/uploads/formidable.
+ $folders[] = get_imagify_backup_dir_path( true ); // Imagify Media Library backup: /wp-content/uploads/backup.
+ $folders[] = self::get_wc_logs_path(); // WooCommerce Logs: /wp-content/uploads/wc-logs.
+ $folders[] = $uploads_dir . 'woocommerce_uploads'; // WooCommerce uploads: /wp-content/uploads/woocommerce_uploads.
+ }
+
+ $folders = array_map( array( $filesystem, 'normalize_dir_path' ), $folders );
+
+ /**
+ * Add folders to the list of forbidden ones.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $added_folders List of absolute paths.
+ * @param array $folders List of folders already forbidden.
+ */
+ $added_folders = apply_filters( 'imagify_add_forbidden_folders', array(), $folders );
+ $added_folders = array_filter( (array) $added_folders );
+ $added_folders = array_filter( $added_folders, 'is_string' );
+
+ if ( ! $added_folders ) {
+ return $folders;
+ }
+
+ $added_folders = array_map( array( $filesystem, 'normalize_dir_path' ), $added_folders );
+
+ $folders = array_merge( $folders, $added_folders );
+ $folders = array_flip( array_flip( $folders ) );
+
+ return $folders;
+ }
+
+ /**
+ * Get the list of folder patterns where Imagify won't look for files to optimize. This is meant for paths that are dynamic.
+ * `^` will be prepended to each pattern (aka, the pattern must match an absolute path).
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
+ * Paths tested against these patterns are lower-cased.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array A list of regex patterns.
+ */
+ public static function get_forbidden_folder_patterns() {
+ static $folders;
+
+ if ( isset( $folders ) ) {
+ return $folders;
+ }
+
+ $folders = array();
+
+ // Media Library: /wp\-content/uploads/(sites/\d+/)?\d{4}/\d{2}/.
+ $folders[] = self::get_media_library_pattern();
+
+ if ( is_multisite() ) {
+ /**
+ * On multisite we can't exclude Imagify's library backup folders, or any other folder located in the uploads folders (created by other plugins): there are too many ways it can fail.
+ * Only exception we're aware of so far is NextGen Gallery, because it provides a clear pattern to use.
+ */
+ $ngg_galleries = self::get_ngg_galleries_multisite_pattern();
+
+ if ( $ngg_galleries ) {
+ // NextGen Gallery: /wp\-content/uploads/sites/\d+/nggallery/.
+ $folders[] = $ngg_galleries;
+ }
+ }
+
+ /**
+ * Add folder patterns to the list of forbidden ones.
+ * Don't forget to use `Imagify_Files_Scan::normalize_path_for_regex( $path )`!
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $added_folders List of patterns.
+ * @param array $folders List of patterns already forbidden.
+ */
+ $added_folders = apply_filters( 'imagify_add_forbidden_folder_patterns', array(), $folders );
+ $added_folders = array_filter( (array) $added_folders );
+ $added_folders = array_filter( $added_folders, 'is_string' );
+
+ if ( ! $added_folders ) {
+ return $folders;
+ }
+
+ $folders = array_merge( $folders, $added_folders );
+ $folders = array_flip( array_flip( $folders ) );
+
+ return $folders;
+ }
+
+ /**
+ * Tell if a file/folder name is forbidden.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_name A file or folder name.
+ * @return bool
+ */
+ public static function is_filename_forbidden( $file_name ) {
+ static $file_names;
+
+ if ( ! isset( $file_names ) ) {
+ $file_names = array_flip( self::get_forbidden_file_names() );
+ }
+
+ return isset( $file_names[ strtolower( $file_name ) ] );
+ }
+
+ /**
+ * Get the list of file names that Imagify won't optimize.
+ * It can contain folder names. Names are case-lowered.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array A list of file names
+ */
+ public static function get_forbidden_file_names() {
+ static $file_names;
+
+ if ( isset( $file_names ) ) {
+ return $file_names;
+ }
+
+ $file_names = array(
+ '.',
+ '..',
+ '.DS_Store',
+ '.git',
+ '.svn',
+ 'backup',
+ 'backups',
+ 'cache',
+ 'lang',
+ 'langs',
+ 'languages',
+ 'node_modules',
+ 'Thumbs.db',
+ );
+ $file_names = array_map( 'strtolower', $file_names );
+
+ /**
+ * Add file names to the list of forbidden ones.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $added_file_names List of file names.
+ * @param array $file_names List of file names already forbidden.
+ */
+ $added_file_names = apply_filters( 'imagify_add_forbidden_file_names', array(), $file_names );
+
+ if ( ! $added_file_names || ! is_array( $added_file_names ) ) {
+ return $file_names;
+ }
+
+ $added_file_names = array_filter( $added_file_names, 'is_string' );
+ $added_file_names = array_map( 'strtolower', $added_file_names );
+
+ $file_names = array_merge( $file_names, $added_file_names );
+ $file_names = array_flip( array_flip( $file_names ) );
+
+ return $file_names;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PLACEHOLDERS ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Add a placeholder to a path.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path An absolute path.
+ * @return string A "placeholdered" path.
+ */
+ public static function add_placeholder( $file_path ) {
+ $file_path = wp_normalize_path( $file_path );
+ $locations = self::get_placeholder_paths();
+
+ foreach ( $locations as $placeholder => $location_path ) {
+ if ( strpos( $file_path, $location_path ) === 0 ) {
+ return preg_replace( '@^' . preg_quote( $location_path, '@' ) . '@', $placeholder, $file_path );
+ }
+ }
+
+ // Should not happen.
+ return $file_path;
+ }
+
+ /**
+ * Change a path with a placeholder into a real path or URL.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path A path with a placeholder.
+ * @param string $type What to return: 'path' or 'url'.
+ * @return string An absolute path or a URL.
+ */
+ public static function remove_placeholder( $file_path, $type = 'path' ) {
+ if ( 'path' === $type ) {
+ $locations = self::get_placeholder_paths();
+ } else {
+ $locations = self::get_placeholder_urls();
+ }
+
+ foreach ( $locations as $placeholder => $location_path ) {
+ if ( strpos( $file_path, $placeholder ) === 0 ) {
+ return preg_replace( '@^' . preg_quote( $placeholder, '@' ) . '@', $location_path, $file_path );
+ }
+ }
+
+ // Should not happen.
+ return $file_path;
+ }
+
+ /**
+ * Get array of pairs of placeholder => corresponding path.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public static function get_placeholder_paths() {
+ static $replacements;
+
+ if ( isset( $replacements ) ) {
+ return $replacements;
+ }
+
+ $filesystem = imagify_get_filesystem();
+ $replacements = array(
+ '{{PLUGINS}}/' => WP_PLUGIN_DIR,
+ '{{MU_PLUGINS}}/' => WPMU_PLUGIN_DIR,
+ '{{THEMES}}/' => WP_CONTENT_DIR . '/themes',
+ '{{UPLOADS}}/' => $filesystem->get_main_upload_basedir(),
+ '{{CONTENT}}/' => WP_CONTENT_DIR,
+ '{{ROOT}}/' => $filesystem->get_site_root(),
+ );
+ $replacements = array_map( array( $filesystem, 'normalize_dir_path' ), $replacements );
+
+ return $replacements;
+ }
+
+ /**
+ * Get array of pairs of placeholder => corresponding URL.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public static function get_placeholder_urls() {
+ static $replacements;
+
+ if ( isset( $replacements ) ) {
+ return $replacements;
+ }
+
+ $filesystem = imagify_get_filesystem();
+ $replacements = array(
+ '{{PLUGINS}}/' => plugins_url( '/' ),
+ '{{MU_PLUGINS}}/' => plugins_url( '/', WPMU_PLUGIN_DIR . '/.' ),
+ '{{THEMES}}/' => content_url( 'themes/' ),
+ '{{UPLOADS}}/' => $filesystem->get_main_upload_baseurl(),
+ '{{CONTENT}}/' => content_url( '/' ),
+ '{{ROOT}}/' => $filesystem->get_site_root_url(),
+ );
+
+ return $replacements;
+ }
+
+ /**
+ * A file_exists() for paths with a placeholder.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return bool
+ */
+ public static function placeholder_path_exists( $file_path ) {
+ return imagify_get_filesystem()->is_readable( self::remove_placeholder( $file_path ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PATHS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the path to NextGen galleries on monosites.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool An absolute path. False if it can't be retrieved.
+ */
+ public static function get_ngg_galleries_path() {
+ $galleries_path = get_site_option( 'ngg_options' );
+
+ if ( empty( $galleries_path['gallerypath'] ) ) {
+ return false;
+ }
+
+ $filesystem = imagify_get_filesystem();
+ $galleries_path = $filesystem->normalize_dir_path( $galleries_path['gallerypath'] );
+ $galleries_path = trim( $galleries_path, '/' ); // Something like `wp-content/gallery`.
+
+ $ngg_root = defined( 'NGG_GALLERY_ROOT_TYPE' ) ? NGG_GALLERY_ROOT_TYPE : 'site';
+
+ if ( $galleries_path && 'content' === $ngg_root ) {
+ $ngg_root = $filesystem->normalize_dir_path( WP_CONTENT_DIR );
+ $ngg_root = trim( $ngg_root, '/' ); // Something like `abs-path/to/wp-content`.
+
+ $exploded_root = explode( '/', $ngg_root );
+ $exploded_galleries = explode( '/', $galleries_path );
+ $first_gallery_dirname = reset( $exploded_galleries );
+ $last_root_dirname = end( $exploded_root );
+
+ if ( $last_root_dirname === $first_gallery_dirname ) {
+ array_shift( $exploded_galleries );
+ $galleries_path = implode( '/', $exploded_galleries );
+ }
+ }
+
+ if ( 'content' === $ngg_root ) {
+ $ngg_root = $filesystem->normalize_dir_path( WP_CONTENT_DIR );
+ } else {
+ $ngg_root = $filesystem->get_abspath();
+ }
+
+ if ( strpos( $galleries_path, $ngg_root ) !== 0 ) {
+ $galleries_path = $ngg_root . $galleries_path;
+ }
+
+ return $galleries_path . '/';
+ }
+
+ /**
+ * Get the path to WooCommerce logs on monosites.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string An absolute path.
+ */
+ public static function get_wc_logs_path() {
+ if ( defined( 'WC_LOG_DIR' ) ) {
+ return WC_LOG_DIR;
+ }
+
+ return imagify_get_filesystem()->get_upload_basedir( true ) . 'wc-logs/';
+ }
+
+ /**
+ * Get the path to EWWW optimization tools.
+ * It is the same for all sites on multisite.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string An absolute path.
+ */
+ public static function get_ewww_tools_path() {
+ if ( defined( 'EWWW_IMAGE_OPTIMIZER_TOOL_PATH' ) ) {
+ return EWWW_IMAGE_OPTIMIZER_TOOL_PATH;
+ }
+
+ return WP_CONTENT_DIR . '/ewww/';
+ }
+
+ /**
+ * Get the path to ShortPixel backup folder.
+ * It is the same for all sites on multisite (and yes, you'll get a surprise if your upload base dir -aka uploads/sites/12/- is not 2 folders deeper than theuploads folder).
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string An absolute path.
+ */
+ public static function get_shortpixel_path() {
+ if ( defined( 'SHORTPIXEL_BACKUP_FOLDER' ) ) {
+ return trailingslashit( SHORTPIXEL_BACKUP_FOLDER );
+ }
+
+ $filesystem = imagify_get_filesystem();
+ $path = $filesystem->get_upload_basedir( true );
+ $path = is_main_site() ? $path : $filesystem->dir_path( $filesystem->dir_path( $path ) );
+
+ return $path . 'ShortpixelBackups/';
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** REGEX PATTERNS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the regex pattern used to match the paths to the media library.
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
+ * Paths tested against these patterns are lower-cased.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string Something like `/wp\-content/uploads/(sites/\d+/)?\d{4}/\d{2}/`.
+ */
+ public static function get_media_library_pattern() {
+ $filesystem = imagify_get_filesystem();
+ $uploads_dir = self::normalize_path_for_regex( $filesystem->get_main_upload_basedir() );
+
+ if ( ! is_multisite() ) {
+ if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
+ // In year/month folders.
+ return $uploads_dir . '\d{4}/\d{2}/';
+ }
+
+ // Not in year/month folders.
+ return $uploads_dir . '[^/]+$';
+ }
+
+ $pattern = $filesystem->get_multisite_uploads_subdir_pattern();
+
+ if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
+ // In year/month folders.
+ return $uploads_dir . '(' . $pattern . ')?\d{4}/\d{2}/';
+ }
+
+ // Not in year/month folders.
+ return $uploads_dir . '(' . $pattern . ')?[^/]+$';
+ }
+
+ /**
+ * Get the regex pattern used to match the paths to NextGen galleries on multisite.
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
+ * Paths tested against these patterns are lower-cased.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool Something like `/wp-content/uploads/sites/\d+/nggallery/`. False if it can't be retrieved.
+ */
+ public static function get_ngg_galleries_multisite_pattern() {
+ $galleries_path = self::get_ngg_galleries_path(); // Something like `wp-content/uploads/sites/%BLOG_ID%/nggallery/`.
+
+ if ( ! $galleries_path ) {
+ return false;
+ }
+
+ $galleries_path = self::normalize_path_for_regex( $galleries_path );
+ $galleries_path = str_replace( array( '%blog_name%', '%blog_id%' ), array( '.+', '\d+' ), $galleries_path );
+
+ return $galleries_path;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** NORMALIZATION TOOLS ===================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Normalize a file path, aiming for path comparison.
+ * The path is normalized and case-lowered.
+ *
+ * @since 1.7
+ * @since 1.8 No trailing slash anymore, because it can be used for files.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return string The normalized file path.
+ */
+ public static function normalize_path_for_comparison( $file_path ) {
+ return strtolower( wp_normalize_path( $file_path ) );
+ }
+
+ /**
+ * Normalize a file path, aiming for use in a regex pattern.
+ * The path is normalized, case-lowered, and escaped.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return string The normalized file path.
+ */
+ public static function normalize_path_for_regex( $file_path ) {
+ return preg_quote( imagify_get_filesystem()->normalize_path_for_comparison( $file_path ), Imagify_Filesystem::PATTERN_DELIMITER );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-files-stats.php b/wp-content/plugins/imagify/inc/classes/class-imagify-files-stats.php
new file mode 100644
index 00000000..8cc15554
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-files-stats.php
@@ -0,0 +1,474 @@
+can_operate() ) {
+ $count[ $status ] = 0;
+ return $count[ $status ];
+ }
+
+ switch ( $status ) {
+ case 'all':
+ $status = '';
+ break;
+
+ case 'none':
+ $status = 'status IS NULL';
+ break;
+
+ case 'optimized':
+ $status = "status IN ('success','already_optimized')";
+ break;
+
+ case 'unoptimized':
+ $status = "( status = 'error' OR status IS NULL )";
+ break;
+
+ default:
+ // "success", "already_optimized", "error".
+ $status = "status = '$status'";
+ }
+
+ $table_name = $files_db->get_table_name();
+ $status = $status ? "WHERE $status" : '';
+
+ $count[ $status ] = (int) $wpdb->get_var( // WPCS: unprepared SQL ok.
+ "SELECT COUNT( file_id ) FROM $table_name $status"
+ );
+
+ return $count[ $status ];
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PERCENTS ================================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Count percent of optimized images in custom folders.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The percent of optimized images.
+ */
+ public static function percent_optimized_files() {
+ /**
+ * Filter the percent of optimized images in custom folders.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param int|bool $percent Default is false. Provide an integer.
+ */
+ $percent = apply_filters( 'imagify_percent_optimized_files', false );
+
+ if ( false !== $percent ) {
+ return (int) $percent;
+ }
+
+ $total_files = self::count_all_files();
+ $total_optimized_files = self::count_optimized_files();
+
+ if ( ! $total_files || ! $total_optimized_files ) {
+ return 0;
+ }
+
+ return min( round( 100 * $total_optimized_files / $total_files ), 100 );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** GET FILE SIZES ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Sum up all optimized sizes of all successfully optimized files.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The sizes sum in bytes.
+ */
+ public static function get_optimized_size() {
+ /**
+ * Filter the optimized sizes of all successfully optimized files.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param int|bool $pre_size Default is false. Provide an integer.
+ */
+ $pre_size = apply_filters( 'imagify_get_optimized_files_size', false );
+
+ if ( false !== $pre_size ) {
+ return (int) $pre_size;
+ }
+
+ return self::get_size( 'optimized' );
+ }
+
+ /**
+ * Sum up all original sizes of all successfully optimized files.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The sizes sum in bytes.
+ */
+ public static function get_original_size() {
+ /**
+ * Filter the original sizes of all successfully optimized files.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param int|bool $pre_size Default is false. Provide an integer.
+ */
+ $pre_size = apply_filters( 'imagify_get_original_files_size', false );
+
+ if ( false !== $pre_size ) {
+ return (int) $pre_size;
+ }
+
+ return self::get_size( 'original' );
+ }
+
+ /**
+ * Sum up all (optimized|original) sizes of all successfully optimized files.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $type "optimized" or "original".
+ * @return int The sizes sum in bytes.
+ */
+ public static function get_size( $type = null ) {
+ global $wpdb;
+ static $sizes = array();
+
+ $type = 'optimized' === $type ? 'optimized_size' : 'original_size';
+
+ if ( isset( $sizes[ $type ] ) ) {
+ return $sizes[ $type ];
+ }
+
+ $files_db = Imagify_Files_DB::get_instance();
+
+ if ( ! $files_db->can_operate() ) {
+ $sizes[ $type ] = 0;
+ return $sizes[ $type ];
+ }
+
+ $table_name = $files_db->get_table_name();
+ $sizes[ $type ] = (int) $wpdb->get_var( // WPCS: unprepared SQL ok.
+ "SELECT SUM( $type ) FROM $table_name WHERE status = 'success'"
+ );
+
+ return $sizes[ $type ];
+ }
+
+ /**
+ * Sum up all original sizes.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The sizes sum in bytes.
+ */
+ public static function get_overall_original_size() {
+ global $wpdb;
+ static $size;
+
+ if ( isset( $size ) ) {
+ return $size;
+ }
+
+ $files_db = Imagify_Files_DB::get_instance();
+
+ if ( ! $files_db->can_operate() ) {
+ $size = 0;
+ return $size;
+ }
+
+ $table_name = $files_db->get_table_name();
+ $size = round( $wpdb->get_var( "SELECT SUM( original_size ) FROM $table_name" ) ); // WPCS: unprepared SQL ok.
+
+ return $size;
+ }
+
+ /**
+ * Calculate the average size of the images uploaded per month.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int The current average size of images uploaded per month in bytes.
+ */
+ public static function calculate_average_size_per_month() {
+ global $wpdb;
+ static $average;
+
+ if ( isset( $average ) ) {
+ return $average;
+ }
+
+ $files_db = Imagify_Files_DB::get_instance();
+
+ if ( ! $files_db->can_operate() ) {
+ $average = 0;
+ return $average;
+ }
+
+ $table_name = $files_db->get_table_name();
+ $average = round( $wpdb->get_var( "SELECT AVG( size ) AS average_size_per_month FROM ( SELECT SUM( original_size ) AS size FROM $table_name GROUP BY YEAR( file_date ), MONTH( file_date ) ) AS size_per_month" ) ); // WPCS: unprepared SQL ok.
+
+ return $average;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Validate a status.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $status The status of these folders: all, success, already_optimized, optimized, error, none, unoptimized.
+ * "none" if for files without status.
+ * "optimized" regroups "success" and "already_optimized".
+ * "unoptimized" regroups "error" and "none".
+ * @return string Fallback to 'all' if the status is not valid.
+ */
+ public static function validate_status( $status = 'all' ) {
+ $statuses = array(
+ 'all' => 1,
+ 'success' => 1,
+ 'already_optimized' => 1,
+ 'error' => 1,
+ 'none' => 1,
+ 'optimized' => 1,
+ 'unoptimized' => 1,
+ );
+
+ return isset( $statuses[ $status ] ) ? $status : 'all';
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-filesystem.php b/wp-content/plugins/imagify/inc/classes/class-imagify-filesystem.php
new file mode 100644
index 00000000..8bd0f075
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-filesystem.php
@@ -0,0 +1,1127 @@
+is_root( $file_path ) ? $this->get_root() : trailingslashit( $file_path );
+ }
+
+ /**
+ * Get information about a file path.
+ * Replacement for pathinfo().
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @param string $option If present, specifies a specific element to be returned; one of 'dir_path', 'file_name', 'extension' or 'file_base'.
+ * If option is not specified, returns all available elements.
+ * @return array|string|null If the option parameter is not passed, an associative array containing the following elements is returned: 'dir_path' (with trailing slash), 'file_name' (with extension), 'extension' (if any), and 'file_base' (without extension).
+ */
+ public function path_info( $file_path, $option = null ) {
+ if ( ! $file_path ) {
+ if ( isset( $option ) ) {
+ return '';
+ }
+
+ return array(
+ 'dir_path' => '',
+ 'file_name' => '',
+ 'extension' => null,
+ 'file_base' => '',
+ );
+ }
+
+ if ( isset( $option ) ) {
+ $options = array(
+ 'dir_path' => PATHINFO_DIRNAME,
+ 'file_name' => PATHINFO_BASENAME,
+ 'extension' => PATHINFO_EXTENSION,
+ 'file_base' => PATHINFO_FILENAME,
+ );
+
+ if ( ! isset( $options[ $option ] ) ) {
+ return '';
+ }
+
+ $output = pathinfo( $file_path, $options[ $option ] );
+
+ if ( 'dir_path' !== $option ) {
+ return $output;
+ }
+
+ return $this->is_root( $output ) ? $this->get_root() : trailingslashit( $output );
+ }
+
+ $output = pathinfo( $file_path );
+
+ $output['dirname'] = $this->is_root( $output['dirname'] ) ? $this->get_root() : trailingslashit( $output['dirname'] );
+ $output['extension'] = isset( $output['extension'] ) ? $output['extension'] : null;
+
+ // '/www/htdocs/inc/lib.inc.php'
+ return array(
+ 'dir_path' => $output['dirname'], // '/www/htdocs/inc/'
+ 'file_name' => $output['basename'], // 'lib.inc.php'
+ 'extension' => $output['extension'], // 'php'
+ 'file_base' => $output['filename'], // 'lib.inc'
+ );
+ }
+
+ /**
+ * Recursive directory creation based on full path. Will attempt to set permissions on folders.
+ * Replacement for recursive mkdir().
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $path Full path to attempt to create.
+ * @return bool Whether the path was created. True if path already exists.
+ */
+ public function make_dir( $path ) {
+ /*
+ * Safe mode fails with a trailing slash under certain PHP versions.
+ */
+ $path = untrailingslashit( wp_normalize_path( $path ) );
+
+ if ( $this->is_root( $path ) ) {
+ return $this->is_dir( $this->get_root() ) && $this->is_writable( $this->get_root() );
+ }
+
+ if ( $this->exists( $path ) ) {
+ return $this->is_dir( $path ) && $this->is_writable( $path );
+ }
+
+ $site_root = $this->get_site_root();
+
+ if ( strpos( $path, $site_root ) !== 0 ) {
+ return false;
+ }
+
+ $bits = preg_replace( '@^' . preg_quote( $site_root, '@' ) . '@i', '', $path );
+ $bits = explode( '/', trim( $bits, '/' ) );
+ $path = untrailingslashit( $site_root );
+
+ foreach ( $bits as $bit ) {
+ $parent_path = $path;
+ $path .= '/' . $bit;
+
+ if ( $this->exists( $path ) ) {
+ if ( ! $this->is_dir( $path ) ) {
+ return false;
+ }
+
+ continue;
+ }
+
+ if ( ! $this->is_writable( $parent_path ) ) {
+ $this->chmod_dir( $parent_path );
+
+ if ( ! $this->is_writable( $parent_path ) ) {
+ return false;
+ }
+ }
+
+ $this->mkdir( $path );
+
+ if ( ! $this->exists( $path ) ) {
+ return false;
+ }
+
+ $this->touch( trailingslashit( $path ) . 'index.php' );
+ }
+
+ return true;
+ }
+
+ /**
+ * Set a file permissions using FS_CHMOD_FILE.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return bool True on success, false on failure.
+ */
+ public function chmod_file( $file_path ) {
+ if ( ! $file_path ) {
+ return false;
+ }
+
+ return $this->chmod( $file_path, FS_CHMOD_FILE );
+ }
+
+ /**
+ * Set a directory permissions using FS_CHMOD_DIR.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the directory.
+ * @return bool True on success, false on failure.
+ */
+ public function chmod_dir( $file_path ) {
+ if ( ! $file_path ) {
+ return false;
+ }
+
+ return $this->chmod( $file_path, FS_CHMOD_DIR );
+ }
+
+ /**
+ * Get a file mime type.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path A file path (prefered) or a filename.
+ * @return string|bool A mime type. False on failure: the test is limited to mime types supported by Imagify.
+ */
+ public function get_mime_type( $file_path ) {
+ if ( ! $file_path ) {
+ return false;
+ }
+
+ $file_type = wp_check_filetype( $file_path, imagify_get_mime_types() );
+
+ return $file_type['type'];
+ }
+
+ /**
+ * Get a file modification date, formated as "mysql". Fallback to current date.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return string The date.
+ */
+ public function get_date( $file_path ) {
+ static $offset;
+
+ if ( ! $file_path ) {
+ return current_time( 'mysql' );
+ }
+
+ $date = $this->mtime( $file_path );
+
+ if ( ! $date ) {
+ return current_time( 'mysql' );
+ }
+
+ if ( ! isset( $offset ) ) {
+ $offset = get_option( 'gmt_offset' ) * HOUR_IN_SECONDS;
+ }
+
+ return gmdate( 'Y-m-d H:i:s', $date + $offset );
+ }
+
+ /**
+ * Tell if a file is symlinked.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path An absolute path.
+ * @return bool
+ */
+ public function is_symlinked( $file_path ) {
+ static $site_root;
+ static $plugin_paths = array();
+ global $wp_plugin_paths;
+
+ if ( ! $file_path ) {
+ return false;
+ }
+
+ $real_path = realpath( $file_path );
+
+ if ( ! $real_path ) {
+ return false;
+ }
+
+ if ( ! isset( $site_root ) ) {
+ $site_root = $this->normalize_path_for_comparison( $this->get_site_root() );
+ }
+
+ $lower_file_path = $this->normalize_path_for_comparison( $real_path );
+
+ if ( strpos( $lower_file_path, $site_root ) !== 0 ) {
+ return true;
+ }
+
+ if ( $wp_plugin_paths && is_array( $wp_plugin_paths ) ) {
+ if ( ! $plugin_paths ) {
+ foreach ( $wp_plugin_paths as $dir => $real_dir ) {
+ $dir = $this->normalize_path_for_comparison( $dir );
+ $plugin_paths[ $dir ] = $this->normalize_path_for_comparison( $real_dir );
+ }
+ }
+
+ $lower_file_path = $this->normalize_path_for_comparison( $file_path );
+
+ foreach ( $plugin_paths as $dir => $real_dir ) {
+ if ( strpos( $lower_file_path, $dir ) === 0 ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Tell if a file is a pdf.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return bool
+ */
+ public function is_pdf( $file_path ) {
+ if ( function_exists( 'finfo_fopen' ) ) {
+ $finfo = finfo_open( FILEINFO_MIME );
+
+ if ( $finfo ) {
+ $mimetype = finfo_file( $finfo, $file_path );
+
+ if ( false !== $mimetype ) {
+ return 'application/pdf' === $mimetype;
+ }
+ }
+ }
+
+ if ( function_exists( 'mime_content_type' ) ) {
+ $mimetype = mime_content_type( $file_path );
+ return 'application/pdf' === $mimetype;
+ }
+
+ return false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** CLASS OVERWRITES ======================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Move a file and apply chmod.
+ * If the file failed to be moved once, a 2nd attempt is made after applying chmod.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $source Path to the file to move.
+ * @param string $destination Path to the destination.
+ * @param bool $overwrite Allow to overwrite existing file at destination.
+ * @return bool True on success, false on failure.
+ */
+ public function move( $source, $destination, $overwrite = false ) {
+ if ( parent::move( $source, $destination, $overwrite ) ) {
+ return $this->chmod_file( $destination );
+ }
+
+ if ( ! $this->chmod_file( $destination ) ) {
+ return false;
+ }
+
+ if ( parent::move( $source, $destination, $overwrite ) ) {
+ return $this->chmod_file( $destination );
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if a file or directory is writable.
+ * This function is used to work around certain ACL issues in PHP primarily affecting Windows Servers.
+ * Replacement for is_writable().
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return bool
+ */
+ public function is_writable( $file_path ) {
+ if ( ! $file_path ) {
+ return false;
+ }
+
+ return wp_is_writable( $file_path );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WORK WITH IMAGES ======================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if a file is an image.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return bool
+ */
+ public function is_image( $file_path ) {
+ if ( function_exists( 'finfo_fopen' ) ) {
+ $finfo = finfo_open( FILEINFO_MIME );
+
+ if ( $finfo ) {
+ $mimetype = finfo_file( $finfo, $file_path );
+
+ if ( false !== $mimetype ) {
+ return strpos( $mimetype, 'image/' ) === 0;
+ }
+ }
+ }
+
+ if ( function_exists( 'exif_imagetype' ) ) {
+ $mimetype = exif_imagetype( $file_path );
+ return (bool) $mimetype;
+ }
+
+ if ( function_exists( 'mime_content_type' ) ) {
+ $mimetype = mime_content_type( $file_path );
+ return strpos( $mimetype, 'image/' ) === 0;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get an image data.
+ * Replacement for getimagesize().
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return array The image data. An empty array on failure.
+ */
+ public function get_image_size( $file_path ) {
+ if ( ! $file_path ) {
+ return array();
+ }
+
+ $size = @getimagesize( $file_path );
+
+ if ( ! $size || ! isset( $size[0], $size[1] ) ) {
+ return array();
+ }
+
+ return array(
+ 0 => (int) $size[0],
+ 1 => (int) $size[1],
+ 'width' => (int) $size[0],
+ 'height' => (int) $size[1],
+ 'type' => (int) $size[2],
+ 'attr' => $size[3],
+ 'channels' => isset( $size['channels'] ) ? (int) $size['channels'] : null,
+ 'bits' => isset( $size['bits'] ) ? (int) $size['bits'] : null,
+ 'mime' => $size['mime'],
+ );
+ }
+
+ /**
+ * Tell if exif_read_data() is available.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function can_get_exif() {
+ static $callable;
+
+ if ( ! isset( $callable ) ) {
+ $callable = is_callable( 'exif_read_data' );
+ }
+
+ return $callable;
+ }
+
+ /**
+ * Get the EXIF headers from an image file.
+ * Replacement for exif_read_data().
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ * @see https://secure.php.net/manual/en/function.exif-read-data.php
+ *
+ * @param string $file_path Path to the file.
+ * @param string $sections A comma separated list of sections that need to be present in file to produce a result array. See exif_read_data() documentation for values: FILE, COMPUTED, ANY_TAG, IFD0, THUMBNAIL, COMMENT, EXIF.
+ * @param bool $arrays Specifies whether or not each section becomes an array. The sections COMPUTED, THUMBNAIL, and COMMENT always become arrays as they may contain values whose names conflict with other sections.
+ * @param bool $thumbnail When set to TRUE the thumbnail itself is read. Otherwise, only the tagged data is read.
+ * @return array The EXIF headers. An empty array on failure.
+ */
+ public function get_image_exif( $file_path, $sections = null, $arrays = false, $thumbnail = false ) {
+ if ( ! $file_path || ! $this->can_get_exif() ) {
+ return array();
+ }
+
+ $exif = @exif_read_data( $file_path, $sections, $arrays, $thumbnail );
+
+ return is_array( $exif ) ? $exif : array();
+ }
+
+ /**
+ * Tell if a file is an animated gif.
+ *
+ * @since 1.9.5
+ * @access public
+ * @source https://www.php.net/manual/en/function.imagecreatefromgif.php#104473
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return bool|null Null if the file cannot be read.
+ */
+ public function is_animated_gif( $file_path ) {
+ if ( $this->path_info( $file_path, 'extension' ) !== 'gif' ) {
+ // Not a gif file.
+ return false;
+ }
+
+ $fh = @fopen( $file_path, 'rb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
+
+ if ( ! $fh ) {
+ // Could not open the file.
+ return null;
+ }
+
+ /**
+ * An animated gif contains multiple "frames", with each frame having a header made up of:
+ * - a static 4-byte sequence (\x00\x21\xF9\x04),
+ * - 4 variable bytes,
+ * - a static 2-byte sequence (\x00\x2C) (some variants may use \x00\x21 ?).
+ */
+ $count = 0;
+
+ // We read through the file til we reach the end of the file, or we've found at least 2 frame headers.
+ while ( ! feof( $fh ) && $count < 2 ) {
+ // Read 100kb at a time.
+ $chunk = fread( $fh, 1024 * 100 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fread
+ $count += preg_match_all( '#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches );
+ }
+
+ fclose( $fh ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
+
+ return $count > 1;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WORK WITH PATHS ========================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Make an absolute path relative to WordPress' root folder.
+ * Also works for files from registered symlinked plugins.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path An absolute path.
+ * @param string $base A base path to use instead of ABSPATH.
+ * @return string|bool A relative path. Can return the absolute path or false in case of a failure.
+ */
+ public function make_path_relative( $file_path, $base = '' ) {
+ global $wp_plugin_paths;
+
+ if ( ! $file_path ) {
+ return false;
+ }
+
+ $file_path = wp_normalize_path( $file_path );
+ $base = $base ? $this->normalize_dir_path( $base ) : $this->get_site_root();
+ $pos = strpos( $file_path, $base );
+
+ if ( false === $pos && $wp_plugin_paths && is_array( $wp_plugin_paths ) ) {
+ // The file is probably part of a symlinked plugin.
+ arsort( $wp_plugin_paths );
+
+ foreach ( $wp_plugin_paths as $dir => $real_dir ) {
+ if ( strpos( $file_path, $real_dir ) === 0 ) {
+ $file_path = wp_normalize_path( $dir . substr( $file_path, strlen( $real_dir ) ) );
+ }
+ }
+
+ $pos = strpos( $file_path, $base );
+ }
+
+ if ( false === $pos ) {
+ // We're in trouble.
+ return $file_path;
+ }
+
+ return substr_replace( $file_path, '', 0, $pos + strlen( $base ) );
+ }
+
+ /**
+ * Normalize a directory path.
+ * The path is normalized and a trailing slash is added.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return string The normalized dir path.
+ */
+ public function normalize_dir_path( $file_path ) {
+ return wp_normalize_path( trailingslashit( $file_path ) );
+ }
+
+ /**
+ * Normalize a file path, aiming for path comparison.
+ * The path is normalized, case-lowered, and a trailing slash is added.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return string The normalized file path.
+ */
+ public function normalize_path_for_comparison( $file_path ) {
+ return strtolower( $this->normalize_dir_path( $file_path ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** SOME WELL KNOWN PATHS AND URLS ========================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if WordPress is installed in its own directory: aka WP's path !== site's path.
+ *
+ * @since 1.8.1
+ * @access public
+ * @see https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function has_wp_its_own_directory() {
+ return $this->get_abspath() !== $this->get_site_root();
+ }
+
+ /**
+ * The path to the server's root is not always '/', it can also be '//' or 'C://'.
+ * I am get_root.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string The path to the server's root.
+ */
+ public function get_root() {
+ static $groot;
+
+ if ( isset( $groot ) ) {
+ return $groot;
+ }
+
+ $groot = preg_replace( '@^((?:.:)?/+).*@', '$1', $this->get_site_root() );
+
+ return $groot;
+ }
+
+ /**
+ * Tell if a path is the server's root.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $path The path.
+ * @return bool
+ */
+ public function is_root( $path ) {
+ $path = rtrim( $path, '/\\' );
+ return '.' === $path || '' === $path || preg_match( '@^.:$@', $path );
+ }
+
+ /**
+ * Get the path to the site's root.
+ * This is an improved version of get_home_path() that *should* work in almost every cases.
+ * Because creating a constant like ABSPATH was too simple.
+ *
+ * @since 1.8.1
+ * @access public
+ * @see get_home_path()
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_site_root() {
+ static $root_path;
+
+ if ( isset( $root_path ) ) {
+ return $root_path;
+ }
+
+ /**
+ * Filter the path to the site's root.
+ *
+ * @since 1.8.1
+ * @author Grégory Viguier
+ *
+ * @param string $root_path Path to the site's root. Default is null.
+ */
+ $root_path = apply_filters( 'imagify_site_root', null );
+
+ if ( is_string( $root_path ) ) {
+ $root_path = trailingslashit( wp_normalize_path( $root_path ) );
+
+ return $root_path;
+ }
+
+ $home = set_url_scheme( untrailingslashit( get_option( 'home' ) ), 'http' );
+ $siteurl = set_url_scheme( untrailingslashit( get_option( 'siteurl' ) ), 'http' );
+
+ if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
+ $wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
+ $pos = strripos( str_replace( '\\', '/', ABSPATH ), trailingslashit( $wp_path_rel_to_home ) );
+ $root_path = substr( ABSPATH, 0, $pos );
+ $root_path = trailingslashit( wp_normalize_path( $root_path ) );
+ return $root_path;
+ }
+
+ if ( ! defined( 'PATH_CURRENT_SITE' ) || ! is_multisite() || is_main_site() ) {
+ $root_path = $this->get_abspath();
+ return $root_path;
+ }
+
+ /**
+ * For a multisite in its own directory, get_home_path() returns the expected path only for the main site.
+ *
+ * Friend, each time an attempt is made to improve this method, and especially this part, please increment the following counter.
+ * Improvement attempts: 3.
+ */
+ $document_root = realpath( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ); // `realpath()` is needed for those cases where $_SERVER['DOCUMENT_ROOT'] is totally different from ABSPATH.
+ $document_root = trailingslashit( str_replace( '\\', '/', $document_root ) );
+ $path_current_site = trim( str_replace( '\\', '/', PATH_CURRENT_SITE ), '/' );
+ $root_path = trailingslashit( wp_normalize_path( $document_root . $path_current_site ) );
+
+ return $root_path;
+ }
+
+ /**
+ * Get the URL of the site's root. It corresponds to the main site's home page URL.
+ *
+ * @since 1.8.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_site_root_url() {
+ static $root_url;
+
+ if ( isset( $root_url ) ) {
+ return $root_url;
+ }
+
+ if ( ! is_multisite() || is_main_site() ) {
+ $root_url = home_url( '/' );
+ return $root_url;
+ }
+
+ $current_network = false;
+
+ if ( function_exists( 'get_network' ) ) {
+ $current_network = get_network();
+ } elseif ( function_exists( 'get_current_site' ) ) {
+ $current_network = get_current_site();
+ }
+
+ if ( ! $current_network ) {
+ $root_url = home_url( '/' );
+ return $root_url;
+ }
+
+ $root_url = is_ssl() ? 'https' : 'http';
+ $root_url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $root_url );
+ $root_url = trailingslashit( $root_url );
+
+ return $root_url;
+ }
+
+ /**
+ * Tell if a path is the site's root.
+ *
+ * @since 1.8.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $path The path.
+ * @return bool
+ */
+ public function is_site_root( $path ) {
+ return $this->normalize_dir_path( $path ) === $this->get_site_root();
+ }
+
+ /**
+ * Get a clean value of ABSPATH.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string The path to WordPress' root folder.
+ */
+ public function get_abspath() {
+ static $abspath;
+
+ if ( isset( $abspath ) ) {
+ return $abspath;
+ }
+
+ $abspath = wp_normalize_path( ABSPATH );
+
+ // Make sure ABSPATH is not messed up: it could be defined as a relative path for example (yeah, I know, but we've seen it).
+ $test_file = wp_normalize_path( IMAGIFY_FILE );
+ $pos = strpos( $test_file, $abspath );
+
+ if ( $pos > 0 ) {
+ // ABSPATH has a wrong value.
+ $abspath = substr( $test_file, 0, $pos ) . $abspath;
+
+ } elseif ( false === $pos && class_exists( 'ReflectionClass' ) ) {
+ // Imagify is symlinked (dude, you look for trouble).
+ $reflector = new ReflectionClass( 'WP' );
+ $test_file = $reflector->getFileName();
+ $pos = strpos( $test_file, $abspath );
+
+ if ( 0 < $pos ) {
+ // ABSPATH has a wrong value.
+ $abspath = substr( $test_file, 0, $pos ) . $abspath;
+ }
+ }
+
+ $abspath = trailingslashit( $abspath );
+
+ if ( '/' !== substr( $abspath, 0, 1 ) && ':' !== substr( $abspath, 1, 1 ) ) {
+ $abspath = '/' . $abspath;
+ }
+
+ return $abspath;
+ }
+
+ /**
+ * Tell if a path is WP's root (ABSPATH).
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $path The path.
+ * @return bool
+ */
+ public function is_abspath( $path ) {
+ return $this->normalize_dir_path( $path ) === $this->get_abspath();
+ }
+
+ /**
+ * Get the upload basedir.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $bypass_error True to return the path even if there is an error. This is used when we want to display this path in a message for example.
+ * @return string|bool The path. False on failure.
+ */
+ public function get_upload_basedir( $bypass_error = false ) {
+ static $upload_basedir;
+ static $upload_basedir_or_error;
+
+ if ( isset( $upload_basedir ) ) {
+ return $bypass_error ? $upload_basedir : $upload_basedir_or_error;
+ }
+
+ $uploads = wp_upload_dir();
+ $upload_basedir = $this->normalize_dir_path( $uploads['basedir'] );
+
+ if ( false !== $uploads['error'] ) {
+ $upload_basedir_or_error = false;
+ } else {
+ $upload_basedir_or_error = $upload_basedir;
+ }
+
+ return $bypass_error ? $upload_basedir : $upload_basedir_or_error;
+ }
+
+ /**
+ * Get the upload baseurl.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The URL. False on failure.
+ */
+ public function get_upload_baseurl() {
+ static $upload_baseurl;
+
+ if ( isset( $upload_baseurl ) ) {
+ return $upload_baseurl;
+ }
+
+ $uploads = wp_upload_dir();
+
+ if ( false !== $uploads['error'] ) {
+ $upload_baseurl = false;
+ return $upload_baseurl;
+ }
+
+ $upload_baseurl = trailingslashit( $uploads['baseurl'] );
+
+ return $upload_baseurl;
+ }
+
+ /**
+ * Get the path to the uploads base directory of the main site.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_main_upload_basedir() {
+ static $basedir;
+
+ if ( isset( $basedir ) ) {
+ return $basedir;
+ }
+
+ $basedir = get_imagify_upload_basedir( true );
+
+ if ( is_multisite() ) {
+ $pattern = '/' . $this->get_multisite_uploads_subdir_pattern() . '$';
+ $basedir = preg_replace( self::PATTERN_DELIMITER . $pattern . self::PATTERN_DELIMITER, '/', $basedir );
+ }
+
+ return $basedir;
+ }
+
+ /**
+ * Get the URL of the uploads base directory of the main site.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_main_upload_baseurl() {
+ static $baseurl;
+
+ if ( isset( $baseurl ) ) {
+ return $baseurl;
+ }
+
+ $baseurl = get_imagify_upload_baseurl( true );
+
+ if ( is_multisite() ) {
+ $pattern = '/' . $this->get_multisite_uploads_subdir_pattern() . '$';
+ $baseurl = preg_replace( self::PATTERN_DELIMITER . $pattern . self::PATTERN_DELIMITER, '/', $baseurl );
+ }
+
+ return $baseurl;
+ }
+
+ /**
+ * Get the regex pattern used to match the uploads subdir on multisite in a file path.
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
+ * Paths tested against these patterns are lower-cased.
+ *
+ * @since 1.8
+ * @access public
+ * @see _wp_upload_dir()
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_multisite_uploads_subdir_pattern() {
+ static $pattern;
+
+ if ( isset( $pattern ) ) {
+ return $pattern;
+ }
+
+ $pattern = '';
+
+ if ( ! is_multisite() ) {
+ return $pattern;
+ }
+
+ if ( ! get_site_option( 'ms_files_rewriting' ) ) {
+ if ( defined( 'MULTISITE' ) ) {
+ $pattern = 'sites/\d+/';
+ } else {
+ $pattern = '\d+/';
+ }
+ } elseif ( defined( 'UPLOADS' ) ) {
+ $site_id = (string) get_current_blog_id();
+ $path = $this->get_upload_basedir( true ); // Something like `/absolute/path/to/wp-content/blogs.dir/3/files/`, also for site 1.
+ $path = strrev( $path );
+
+ if ( preg_match( self::PATTERN_DELIMITER . '^.*' . strrev( $site_id ) . '[^/]*/' . self::PATTERN_DELIMITER . 'U', $path, $matches ) ) {
+ $pattern = end( $matches );
+ $pattern = ltrim( strtolower( strrev( $pattern ) ), '/' );
+ $pattern = str_replace( $site_id, '\d+', $pattern );
+ }
+ }
+
+ /**
+ * Filter the regex pattern used to match the uploads subdir on multisite in a file path.
+ * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
+ * Important: lowercase, no heading slash, mandatory trailing slash.
+ *
+ * @since 1.8
+ * @author Grégory Viguier
+ *
+ * @param string $pattern The regex pattern.
+ */
+ $pattern = apply_filters( 'imagify_multisite_uploads_subdir_pattern', $pattern );
+
+ return $pattern;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-folders-db.php b/wp-content/plugins/imagify/inc/classes/class-imagify-folders-db.php
new file mode 100644
index 00000000..cc3f269f
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-folders-db.php
@@ -0,0 +1,238 @@
+ '%d',
+ 'path' => '%s',
+ 'active' => '%d',
+ );
+ }
+
+ /**
+ * Default column values.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_column_defaults() {
+ return array(
+ 'folder_id' => 0,
+ 'path' => '',
+ 'active' => 0,
+ );
+ }
+
+ /**
+ * Get the query to create the table fields.
+ *
+ * @since 1.7
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ protected function get_table_schema() {
+ return "
+ folder_id bigint(20) unsigned NOT NULL auto_increment,
+ path varchar(191) NOT NULL default '',
+ active tinyint(1) unsigned NOT NULL default 0,
+ PRIMARY KEY (folder_id),
+ UNIQUE KEY path (path),
+ KEY active (active)";
+ }
+
+ /**
+ * Tell if folders are selected in the plugin settings.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_active_folders() {
+ global $wpdb;
+
+ $column = esc_sql( $this->get_primary_key() );
+
+ return (bool) $wpdb->get_var( "SELECT $column FROM $this->table_name WHERE active = 1 LIMIT 1;" ); // WPCS: unprepared SQL ok.
+ }
+
+ /**
+ * Retrieve active folders (checked in the settings).
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @return array
+ */
+ public function get_active_folders_column( $column_select ) {
+ global $wpdb;
+
+ $column = esc_sql( $column_select );
+
+ $result = $wpdb->get_col( "SELECT $column FROM $this->table_name WHERE active = 1;" ); // WPCS: unprepared SQL ok.
+
+ return $this->cast_col( $result, $column_select );
+ }
+
+ /**
+ * Retrieve active folders (checked in the settings) by the specified column / values.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @param string $column_where A column name.
+ * @param array $column_values An array of values.
+ * @return array
+ */
+ public function get_active_folders_column_in( $column_select, $column_where, $column_values ) {
+ global $wpdb;
+
+ $column = esc_sql( $column_select );
+ $column_where = esc_sql( $column_where );
+ $column_values = Imagify_DB::prepare_values_list( $column_values );
+
+ $result = $wpdb->get_col( "SELECT $column FROM $this->table_name WHERE $column_where IN ( $column_values ) AND active = 1;" ); // WPCS: unprepared SQL ok.
+
+ return $this->cast_col( $result, $column_select );
+ }
+
+ /**
+ * Retrieve active folders (checked in the settings) by the specified column / values.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @param string $column_where A column name.
+ * @param array $column_values An array of values.
+ * @return array
+ */
+ public function get_active_folders_column_not_in( $column_select, $column_where, $column_values ) {
+ global $wpdb;
+
+ $column = esc_sql( $column_select );
+ $column_where = esc_sql( $column_where );
+ $column_values = Imagify_DB::prepare_values_list( $column_values );
+
+ $result = $wpdb->get_col( "SELECT $column FROM $this->table_name WHERE $column_where NOT IN ( $column_values ) AND active = 1;" ); // WPCS: unprepared SQL ok.
+
+ return $this->cast_col( $result, $column_select );
+ }
+
+ /**
+ * Retrieve not active folders (not checked in the settings).
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $column_select A column name.
+ * @return array
+ */
+ public function get_inactive_folders_column( $column_select ) {
+ global $wpdb;
+
+ $column = esc_sql( $column_select );
+
+ $result = $wpdb->get_col( "SELECT $column FROM $this->table_name WHERE active != 1;" ); // WPCS: unprepared SQL ok.
+
+ return $this->cast_col( $result, $column_select );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-notices.php b/wp-content/plugins/imagify/inc/classes/class-imagify-notices.php
new file mode 100644
index 00000000..d21a8217
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-notices.php
@@ -0,0 +1,914 @@
+ 'optimize',
+ 'backup-folder-not-writable' => 'bulk-optimize',
+ 'rating' => 'bulk-optimize',
+ 'wp-rocket' => 'bulk-optimize',
+ );
+
+ /**
+ * List of plugins that conflict with Imagify.
+ *
+ * @var array
+ */
+ protected static $conflicting_plugins = array(
+ 'wp-smush' => 'wp-smushit/wp-smush.php', // WP Smush.
+ 'wp-smush-pro' => 'wp-smush-pro/wp-smush.php', // WP Smush Pro.
+ 'kraken' => 'kraken-image-optimizer/kraken.php', // Kraken.io.
+ 'tinypng' => 'tiny-compress-images/tiny-compress-images.php', // TinyPNG.
+ 'shortpixel' => 'shortpixel-image-optimiser/wp-shortpixel.php', // Shortpixel.
+ 'ewww' => 'ewww-image-optimizer/ewww-image-optimizer.php', // EWWW Image Optimizer.
+ 'ewww-cloud' => 'ewww-image-optimizer-cloud/ewww-image-optimizer-cloud.php', // EWWW Image Optimizer Cloud.
+ 'imagerecycle' => 'imagerecycle-pdf-image-compression/wp-image-recycle.php', // ImageRecycle.
+ );
+
+ /**
+ * The single instance of the class.
+ *
+ * @var object
+ */
+ protected static $_instance;
+
+ /**
+ * The constructor.
+ *
+ * @return void
+ */
+ protected function __construct() {}
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** INIT ==================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the main Instance.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return object Main instance.
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$_instance ) ) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Launch the hooks.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ */
+ public function init() {
+ // For generic purpose.
+ add_action( 'all_admin_notices', array( $this, 'render_notices' ) );
+ add_action( 'wp_ajax_imagify_dismiss_notice', array( $this, 'admin_post_dismiss_notice' ) );
+ add_action( 'admin_post_imagify_dismiss_notice', array( $this, 'admin_post_dismiss_notice' ) );
+ // For specific notices.
+ add_action( 'imagify_dismiss_notice', array( $this, 'clear_scheduled_rating' ) );
+ add_action( 'admin_post_imagify_deactivate_plugin', array( $this, 'deactivate_plugin' ) );
+ add_action( 'imagify_not_almost_over_quota_anymore', array( $this, 'renew_almost_over_quota_notice' ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** HOOKS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Maybe display some notices.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ */
+ public function render_notices() {
+ foreach ( $this->get_notice_ids() as $notice_id ) {
+ // Get the name of the method that will tell if this notice should be displayed.
+ $callback = 'display_' . str_replace( '-', '_', $notice_id );
+
+ if ( ! method_exists( $this, $callback ) ) {
+ continue;
+ }
+
+ $data = call_user_func( array( $this, $callback ) );
+
+ if ( $data ) {
+ // The notice must be displayed: render the view.
+ Imagify_Views::get_instance()->print_template( 'notice-' . $notice_id, $data );
+ }
+ }
+
+ // Temporary notices.
+ $this->render_temporary_notices();
+ }
+
+ /**
+ * Process a dismissed notice.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ * @see _do_admin_post_imagify_dismiss_notice()
+ */
+ public function admin_post_dismiss_notice() {
+ imagify_check_nonce( self::DISMISS_NONCE_ACTION );
+
+ $notice = ! empty( $_GET['notice'] ) ? esc_html( wp_unslash( $_GET['notice'] ) ) : false;
+ $notices = $this->get_notice_ids();
+ $notices = array_flip( $notices );
+
+ if ( ! $notice || ! isset( $notices[ $notice ] ) || ! $this->user_can( $notice ) ) {
+ imagify_die();
+ }
+
+ self::dismiss_notice( $notice );
+
+ /**
+ * Fires when a notice is dismissed.
+ *
+ * @since 1.4.2
+ *
+ * @param int $notice The notice slug
+ */
+ do_action( 'imagify_dismiss_notice', $notice );
+
+ imagify_maybe_redirect();
+ wp_send_json_success();
+ }
+
+ /**
+ * Stop the rating cron when the notice is dismissed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ * @see _imagify_clear_scheduled_rating()
+ *
+ * @param string $notice The notice name.
+ */
+ public function clear_scheduled_rating( $notice ) {
+ if ( 'rating' === $notice ) {
+ set_site_transient( 'do_imagify_rating_cron', 'no' );
+ Imagify_Cron_Rating::get_instance()->unschedule_event();
+ }
+ }
+
+ /**
+ * Disable a plugin which can be in conflict with Imagify.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ * @see _imagify_deactivate_plugin()
+ */
+ public function deactivate_plugin() {
+ imagify_check_nonce( self::DEACTIVATE_PLUGIN_NONCE_ACTION );
+
+ if ( empty( $_GET['plugin'] ) || ! $this->user_can( 'plugins-to-deactivate' ) ) {
+ imagify_die();
+ }
+
+ $plugin = esc_html( wp_unslash( $_GET['plugin'] ) );
+ $plugins = $this->get_conflicting_plugins();
+ $plugins = array_flip( $plugins );
+
+ if ( empty( $plugins[ $plugin ] ) ) {
+ imagify_die();
+ }
+
+ deactivate_plugins( $plugin );
+
+ imagify_maybe_redirect();
+ wp_send_json_success();
+ }
+
+ /**
+ * Renew the "almost-over-quota" notice when the consumed quota percent decreases back below 80%.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ */
+ public function renew_almost_over_quota_notice() {
+ global $wpdb;
+
+ $results = $wpdb->get_results( $wpdb->prepare( "SELECT umeta_id, user_id FROM $wpdb->usermeta WHERE meta_key = %s AND meta_value LIKE %s", self::DISMISS_META_NAME, '%almost-over-quota%' ) );
+
+ if ( ! $results ) {
+ return;
+ }
+
+ // Prevent multiple queries to the DB by caching user metas.
+ $not_cached = array();
+
+ foreach ( $results as $result ) {
+ if ( ! wp_cache_get( $result->umeta_id, 'user_meta' ) ) {
+ $not_cached[] = $result->umeta_id;
+ }
+ }
+
+ if ( $not_cached ) {
+ update_meta_cache( 'user', $not_cached );
+ }
+
+ // Renew the notice for all users.
+ foreach ( $results as $result ) {
+ self::renew_notice( 'almost-over-quota', $result->user_id );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** NOTICES ================================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if the 'welcome-steps' notice should be displayed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function display_welcome_steps() {
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ $display = false;
+
+ if ( ! $this->user_can( 'welcome-steps' ) ) {
+ return $display;
+ }
+
+ if ( imagify_is_screen( 'imagify-settings' ) ) {
+ return $display;
+ }
+
+ if ( self::notice_is_dismissed( 'welcome-steps' ) || get_imagify_option( 'api_key' ) ) {
+ return $display;
+ }
+
+ $display = true;
+ return $display;
+ }
+
+ /**
+ * Tell if the 'wrong-api-key' notice should be displayed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function display_wrong_api_key() {
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ $display = false;
+
+ if ( ! $this->user_can( 'wrong-api-key' ) ) {
+ return $display;
+ }
+
+ if ( ! imagify_is_screen( 'bulk' ) ) {
+ return $display;
+ }
+
+ if ( self::notice_is_dismissed( 'wrong-api-key' ) || ! get_imagify_option( 'api_key' ) || Imagify_Requirements::is_api_key_valid() ) {
+ return $display;
+ }
+
+ $display = true;
+ return $display;
+ }
+
+ /**
+ * Tell if the 'plugins-to-deactivate' notice should be displayed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return array An array of plugins to deactivate.
+ */
+ public function display_plugins_to_deactivate() {
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ if ( ! $this->user_can( 'plugins-to-deactivate' ) ) {
+ $display = false;
+ return $display;
+ }
+
+ $display = $this->get_conflicting_plugins();
+ return $display;
+ }
+
+ /**
+ * Tell if the 'plugins-to-deactivate' notice should be displayed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function display_http_block_external() {
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ $display = false;
+
+ if ( ! $this->user_can( 'http-block-external' ) ) {
+ return $display;
+ }
+
+ if ( imagify_is_screen( 'imagify-settings' ) ) {
+ return $display;
+ }
+
+ if ( self::notice_is_dismissed( 'http-block-external' ) || ! Imagify_Requirements::is_imagify_blocked() ) {
+ return $display;
+ }
+
+ $display = true;
+ return $display;
+ }
+
+ /**
+ * Tell if the 'grid-view' notice should be displayed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function display_grid_view() {
+ global $wp_version;
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ $display = false;
+
+ if ( ! $this->user_can( 'grid-view' ) ) {
+ return $display;
+ }
+
+ if ( ! imagify_is_screen( 'library' ) ) {
+ return $display;
+ }
+
+ $media_library_mode = get_user_option( 'media_library_mode', get_current_user_id() );
+
+ if ( 'list' === $media_library_mode || self::notice_is_dismissed( 'grid-view' ) || version_compare( $wp_version, '4.0' ) < 0 ) {
+ return $display;
+ }
+
+ // Don't display the notice if the API key isn't valid.
+ if ( ! Imagify_Requirements::is_api_key_valid() ) {
+ return $display;
+ }
+
+ $display = true;
+ return $display;
+ }
+
+ /**
+ * Tell if the 'almost-over-quota' notice should be displayed.
+ *
+ * @since 1.7.0
+ * @author Geoffrey Crofte
+ *
+ * @return bool|object An Imagify user object. False otherwise.
+ */
+ public function display_almost_over_quota() {
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ $display = false;
+
+ if ( ! $this->user_can( 'almost-over-quota' ) ) {
+ return $display;
+ }
+
+ if ( ! imagify_is_screen( 'imagify-settings' ) && ! imagify_is_screen( 'bulk' ) ) {
+ return $display;
+ }
+
+ if ( self::notice_is_dismissed( 'almost-over-quota' ) ) {
+ return $display;
+ }
+
+ $user = new Imagify_User();
+
+ // Don't display the notice if the user's unconsumed quota is superior to 20%.
+ if ( $user->get_percent_unconsumed_quota() > 20 ) {
+ return $display;
+ }
+
+ $display = $user;
+ return $display;
+ }
+
+ /**
+ * Tell if the 'backup-folder-not-writable' notice should be displayed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function display_backup_folder_not_writable() {
+ global $post_id;
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ $display = false;
+
+ if ( ! $this->user_can( 'backup-folder-not-writable' ) ) {
+ return $display;
+ }
+
+ // Every places where images can be optimized, automatically or not (+ the settings page).
+ if ( ! imagify_is_screen( 'imagify-settings' ) && ! imagify_is_screen( 'library' ) && ! imagify_is_screen( 'upload' ) && ! imagify_is_screen( 'bulk' ) && ! imagify_is_screen( 'media-modal' ) ) {
+ return $display;
+ }
+
+ if ( ! get_imagify_option( 'backup' ) ) {
+ return $display;
+ }
+
+ if ( Imagify_Requirements::attachments_backup_dir_is_writable() ) {
+ return $display;
+ }
+
+ $display = true;
+ return $display;
+ }
+
+ /**
+ * Tell if the 'rating' notice should be displayed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool|int
+ */
+ public function display_rating() {
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ $display = false;
+
+ if ( ! $this->user_can( 'rating' ) ) {
+ return $display;
+ }
+
+ if ( ! imagify_is_screen( 'bulk' ) && ! imagify_is_screen( 'library' ) && ! imagify_is_screen( 'upload' ) ) {
+ return $display;
+ }
+
+ if ( self::notice_is_dismissed( 'rating' ) ) {
+ return $display;
+ }
+
+ $user_images_count = (int) get_site_transient( 'imagify_user_images_count' );
+
+ if ( ! $user_images_count || get_site_transient( 'imagify_seen_rating_notice' ) ) {
+ return $display;
+ }
+
+ $display = $user_images_count;
+ return $display;
+ }
+
+ /**
+ * Tell if the 'wp-rocket' notice should be displayed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function display_wp_rocket() {
+ static $display;
+
+ if ( isset( $display ) ) {
+ return $display;
+ }
+
+ $display = false;
+
+ if ( ! $this->user_can( 'wp-rocket' ) ) {
+ return $display;
+ }
+
+ if ( ! imagify_is_screen( 'bulk' ) ) {
+ return $display;
+ }
+
+ if ( defined( 'WP_ROCKET_VERSION' ) || self::notice_is_dismissed( 'wp-rocket' ) ) {
+ return $display;
+ }
+
+ $display = true;
+ return $display;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TEMPORARY NOTICES ======================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Maybe display some notices.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function render_temporary_notices() {
+ if ( is_network_admin() ) {
+ $notices = $this->get_network_temporary_notices();
+ } else {
+ $notices = $this->get_site_temporary_notices();
+ }
+
+ if ( ! $notices ) {
+ return;
+ }
+
+ $views = Imagify_Views::get_instance();
+
+ foreach ( $notices as $i => $notice_data ) {
+ $notices[ $i ]['type'] = ! empty( $notice_data['type'] ) ? $notice_data['type'] : 'error';
+ }
+
+ $views->print_template( 'notice-temporary', $notices );
+ }
+
+ /**
+ * Get temporary notices for the network.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_network_temporary_notices() {
+ $notices = get_site_transient( self::TEMPORARY_NOTICES_TRANSIENT_NAME );
+
+ if ( false === $notices ) {
+ return array();
+ }
+
+ delete_site_transient( self::TEMPORARY_NOTICES_TRANSIENT_NAME );
+
+ return $notices && is_array( $notices ) ? $notices : array();
+ }
+
+ /**
+ * Create a temporary notice for the network.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array|object|string $notice_data Some data, with the message to display.
+ */
+ public function add_network_temporary_notice( $notice_data ) {
+ $notices = get_site_transient( self::TEMPORARY_NOTICES_TRANSIENT_NAME );
+ $notices = is_array( $notices ) ? $notices : array();
+
+ if ( is_wp_error( $notice_data ) ) {
+ $notice_data = $notice_data->get_error_messages();
+ $notice_data = implode( ' ', $notice_data );
+ }
+
+ if ( is_string( $notice_data ) ) {
+ $notice_data = array(
+ 'message' => $notice_data,
+ );
+ } elseif ( is_object( $notice_data ) ) {
+ $notice_data = (array) $notice_data;
+ }
+
+ if ( ! is_array( $notice_data ) || empty( $notice_data['message'] ) ) {
+ return;
+ }
+
+ $notices[] = $notice_data;
+
+ set_site_transient( self::TEMPORARY_NOTICES_TRANSIENT_NAME, $notices, 30 );
+ }
+
+ /**
+ * Get temporary notices for the current site.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_site_temporary_notices() {
+ $notices = get_transient( self::TEMPORARY_NOTICES_TRANSIENT_NAME );
+
+ if ( false === $notices ) {
+ return array();
+ }
+
+ delete_transient( self::TEMPORARY_NOTICES_TRANSIENT_NAME );
+
+ return $notices && is_array( $notices ) ? $notices : array();
+ }
+
+ /**
+ * Create a temporary notice for the current site.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array|string $notice_data Some data, with the message to display.
+ */
+ public function add_site_temporary_notice( $notice_data ) {
+ $notices = get_transient( self::TEMPORARY_NOTICES_TRANSIENT_NAME );
+ $notices = is_array( $notices ) ? $notices : array();
+
+ if ( is_string( $notice_data ) ) {
+ $notice_data = array(
+ 'message' => $notice_data,
+ );
+ } elseif ( is_object( $notice_data ) ) {
+ $notice_data = (array) $notice_data;
+ }
+
+ if ( ! is_array( $notice_data ) || empty( $notice_data['message'] ) ) {
+ return;
+ }
+
+ $notices[] = $notice_data;
+
+ set_transient( self::TEMPORARY_NOTICES_TRANSIENT_NAME, $notices, 30 );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PUBLIC TOOLS ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Renew a dismissed Imagify notice.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $notice A notice ID.
+ * @param int $user_id A user ID.
+ */
+ public static function renew_notice( $notice, $user_id = 0 ) {
+ $user_id = $user_id ? (int) $user_id : get_current_user_id();
+ $notices = get_user_meta( $user_id, self::DISMISS_META_NAME, true );
+ $notices = $notices && is_array( $notices ) ? array_flip( $notices ) : array();
+
+ if ( ! isset( $notices[ $notice ] ) ) {
+ return;
+ }
+
+ unset( $notices[ $notice ] );
+ $notices = array_flip( $notices );
+ $notices = array_filter( $notices );
+ $notices = array_values( $notices );
+
+ update_user_meta( $user_id, self::DISMISS_META_NAME, $notices );
+ }
+
+ /**
+ * Dismiss an Imagify notice.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ * @see imagify_dismiss_notice()
+ *
+ * @param string $notice A notice ID.
+ * @param int $user_id A user ID.
+ */
+ public static function dismiss_notice( $notice, $user_id = 0 ) {
+ $user_id = $user_id ? (int) $user_id : get_current_user_id();
+ $notices = get_user_meta( $user_id, self::DISMISS_META_NAME, true );
+ $notices = $notices && is_array( $notices ) ? array_flip( $notices ) : array();
+
+ if ( isset( $notices[ $notice ] ) ) {
+ return;
+ }
+
+ $notices = array_flip( $notices );
+ $notices[] = $notice;
+ $notices = array_filter( $notices );
+ $notices = array_values( $notices );
+
+ update_user_meta( $user_id, self::DISMISS_META_NAME, $notices );
+ }
+
+ /**
+ * Tell if an Imagify notice is dismissed.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ * @see imagify_notice_is_dismissed()
+ *
+ * @param string $notice A notice ID.
+ * @param int $user_id A user ID.
+ * @return bool
+ */
+ public static function notice_is_dismissed( $notice, $user_id = 0 ) {
+ $user_id = $user_id ? (int) $user_id : get_current_user_id();
+ $notices = get_user_meta( $user_id, self::DISMISS_META_NAME, true );
+ $notices = $notices && is_array( $notices ) ? array_flip( $notices ) : array();
+
+ return isset( $notices[ $notice ] );
+ }
+
+ /**
+ * Tell if one or more notices will be displayed later in the page.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_notices() {
+ foreach ( self::$notice_ids as $notice_id ) {
+ $callback = 'display_' . str_replace( '-', '_', $notice_id );
+
+ if ( method_exists( $this, $callback ) && call_user_func( array( $this, $callback ) ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** INTERNAL TOOLS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get all notice IDs.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ */
+ protected function get_notice_ids() {
+ /**
+ * Filter the notices Imagify can display.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param array $notice_ids An array of notice "IDs".
+ */
+ return apply_filters( 'imagify_notices', self::$notice_ids );
+ }
+
+ /**
+ * Tell if the current user can see the notices.
+ * Notice IDs that are not listed in self::$capabilities are assumed as 'manage'.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $notice_id A notice ID.
+ * @return bool
+ */
+ protected function user_can( $notice_id ) {
+ $capability = isset( self::$capabilities[ $notice_id ] ) ? self::$capabilities[ $notice_id ] : 'manage';
+
+ return imagify_get_context( 'wp' )->current_user_can( $capability );
+ }
+
+ /**
+ * Get a list of plugins that can conflict with Imagify.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ protected function get_conflicting_plugins() {
+ /**
+ * Filter the recommended plugins to deactivate to prevent conflicts.
+ *
+ * @since 1.0
+ *
+ * @param string $plugins List of recommended plugins to deactivate.
+ */
+ $plugins = apply_filters( 'imagify_plugins_to_deactivate', self::$conflicting_plugins );
+
+ return array_filter( $plugins, 'is_plugin_active' );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-options.php b/wp-content/plugins/imagify/inc/classes/class-imagify-options.php
new file mode 100644
index 00000000..9a903a29
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-options.php
@@ -0,0 +1,254 @@
+ '',
+ 'optimization_level' => 0,
+ 'auto_optimize' => 0,
+ 'backup' => 0,
+ 'resize_larger' => 0,
+ 'resize_larger_w' => 0,
+ 'convert_to_webp' => 0,
+ 'display_webp' => 0,
+ 'display_webp_method' => 'picture',
+ 'cdn_url' => '',
+ 'exif' => 0,
+ 'disallowed-sizes' => array(),
+ 'admin_bar_menu' => 0,
+ 'partner_links' => 0,
+ );
+
+ /**
+ * The Imagify main option values used when they are set the first time or reset.
+ * Values identical to default values are not listed.
+ *
+ * @var array
+ * @since 1.7
+ * @access protected
+ */
+ protected $reset_values = array(
+ 'optimization_level' => 1,
+ 'auto_optimize' => 1,
+ 'backup' => 1,
+ 'convert_to_webp' => 1,
+ 'admin_bar_menu' => 1,
+ 'partner_links' => 1,
+ );
+
+ /**
+ * The single instance of the class.
+ *
+ * @var object
+ * @since 1.7
+ * @access protected
+ */
+ protected static $_instance;
+
+ /**
+ * The constructor.
+ * Side note: $this->hook_identifier value is "option".
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access protected
+ */
+ protected function __construct() {
+ if ( defined( 'IMAGIFY_API_KEY' ) && IMAGIFY_API_KEY ) {
+ $this->default_values['api_key'] = (string) IMAGIFY_API_KEY;
+ }
+
+ if ( function_exists( 'wp_get_original_image_path' ) ) {
+ $this->reset_values['resize_larger'] = 1;
+
+ $filter_cb = [ imagify_get_context( 'wp' ), 'get_resizing_threshold' ];
+ $filtered = has_filter( 'big_image_size_threshold', $filter_cb );
+
+ if ( $filtered ) {
+ remove_filter( 'big_image_size_threshold', $filter_cb, IMAGIFY_INT_MAX );
+ }
+
+ /** This filter is documented in wp-admin/includes/image.php */
+ $this->reset_values['resize_larger_w'] = (int) apply_filters( 'big_image_size_threshold', 2560, [ 0, 0 ], '', 0 );
+ $this->reset_values['resize_larger_w'] = $this->sanitize_and_validate_value( 'resize_larger_w', $this->reset_values['resize_larger_w'], $this->default_values['resize_larger_w'] );
+
+ if ( $filtered ) {
+ add_filter( 'big_image_size_threshold', $filter_cb, IMAGIFY_INT_MAX );
+ }
+ }
+
+ $this->network_option = imagify_is_active_for_network();
+
+ parent::__construct();
+ }
+
+ /**
+ * Get the main Instance.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return object Main instance.
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$_instance ) ) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** SANITIZATION, VALIDATION ================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Sanitize and validate an option value. Basic casts have been made.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $key The option key.
+ * @param mixed $value The value.
+ * @param mixed $default The default value.
+ * @return mixed
+ */
+ public function sanitize_and_validate_value( $key, $value, $default ) {
+ static $max_sizes;
+
+ switch ( $key ) {
+ case 'api_key':
+ if ( defined( 'IMAGIFY_API_KEY' ) && IMAGIFY_API_KEY ) {
+ return (string) IMAGIFY_API_KEY;
+ }
+ return $value ? sanitize_key( $value ) : '';
+
+ case 'optimization_level':
+ if ( $value < 0 || $value > 2 ) {
+ // For an invalid value, return the "reset" value.
+ $reset_values = $this->get_reset_values();
+ return $reset_values[ $key ];
+ }
+ return $value;
+
+ case 'auto_optimize':
+ case 'backup':
+ case 'resize_larger':
+ case 'convert_to_webp':
+ case 'display_webp':
+ case 'exif':
+ case 'admin_bar_menu':
+ case 'partner_links':
+ return 1;
+
+ case 'resize_larger_w':
+ if ( $value <= 0 ) {
+ // Invalid.
+ return $default;
+ }
+ if ( ! isset( $max_sizes ) ) {
+ $max_sizes = get_imagify_max_intermediate_image_size();
+ }
+ if ( $value < $max_sizes['width'] ) {
+ // Invalid.
+ return $max_sizes['width'];
+ }
+ return $value;
+
+ case 'disallowed-sizes':
+ if ( ! $value ) {
+ return $default;
+ }
+
+ $value = array_keys( $value );
+ $value = array_map( 'sanitize_text_field', $value );
+ return array_fill_keys( $value, 1 );
+
+ case 'display_webp_method':
+ $values = [
+ 'picture' => 1,
+ 'rewrite' => 1,
+ ];
+ if ( isset( $values[ $value ] ) ) {
+ return $value;
+ }
+ // For an invalid value, return the "reset" value.
+ $reset_values = $this->get_reset_values();
+ return $reset_values[ $key ];
+
+ case 'cdn_url':
+ $cdn_source = \Imagify\Webp\Picture\Display::get_instance()->get_cdn_source( $value );
+
+ if ( 'option' !== $cdn_source['source'] ) {
+ /**
+ * If the URL is defined via constant or filter, unset the option.
+ * This is useful when the CDN is disabled: there is no need to do anything then.
+ */
+ return '';
+ }
+
+ return $cdn_source['url'];
+ }
+
+ return false;
+ }
+
+ /**
+ * Validate Imagify's options before storing them. Basic sanitization and validation have been made, row by row.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $values The option value.
+ * @return array
+ */
+ public function validate_values_on_update( $values ) {
+ // The max width for the "Resize larger images" option can't be 0.
+ if ( empty( $values['resize_larger_w'] ) ) {
+ unset( $values['resize_larger'], $values['resize_larger_w'] );
+ }
+
+ // Don't display wepb if conversion is disabled.
+ if ( empty( $values['convert_to_webp'] ) ) {
+ unset( $values['convert_to_webp'], $values['display_webp'] );
+ }
+
+ return $values;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-plugin.php b/wp-content/plugins/imagify/inc/classes/class-imagify-plugin.php
new file mode 100644
index 00000000..a04a2d4b
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-plugin.php
@@ -0,0 +1,177 @@
+plugin_path = $plugin_args['plugin_path'];
+ }
+
+ /**
+ * Plugin init.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function init() {
+ $this->include_files();
+
+ class_alias( '\\Imagify\\Traits\\InstanceGetterTrait', '\\Imagify\\Traits\\FakeSingletonTrait' );
+
+ Imagify_Auto_Optimization::get_instance()->init();
+ Imagify_Options::get_instance()->init();
+ Imagify_Data::get_instance()->init();
+ Imagify_Folders_DB::get_instance()->init();
+ Imagify_Files_DB::get_instance()->init();
+ Imagify_Cron_Library_Size::get_instance()->init();
+ Imagify_Cron_Rating::get_instance()->init();
+ Imagify_Cron_Sync_Files::get_instance()->init();
+ \Imagify\Auth\Basic::get_instance()->init();
+ \Imagify\Job\MediaOptimization::get_instance()->init();
+ \Imagify\Stats\OptimizedMediaWithoutWebp::get_instance()->init();
+
+ if ( is_admin() ) {
+ Imagify_Notices::get_instance()->init();
+ Imagify_Admin_Ajax_Post::get_instance()->init();
+ Imagify_Settings::get_instance()->init();
+ Imagify_Views::get_instance()->init();
+ \Imagify\Imagifybeat\Core::get_instance()->init();
+ \Imagify\Imagifybeat\Actions::get_instance()->init();
+ }
+
+ if ( ! wp_doing_ajax() ) {
+ Imagify_Assets::get_instance()->init();
+ }
+
+ \Imagify\Webp\Display::get_instance()->init();
+
+ add_action( 'init', [ $this, 'maybe_activate' ] );
+
+ // Load plugin translations.
+ imagify_load_translations();
+
+ /**
+ * Fires when Imagify is fully loaded.
+ *
+ * @since 1.0
+ * @since 1.9 Added the class instance as parameter.
+ *
+ * @param \Imagify_Plugin $plugin Instance of this class.
+ */
+ do_action( 'imagify_loaded', $this );
+ }
+
+ /**
+ * Include plugin files.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function include_files() {
+ if ( file_exists( $this->plugin_path . 'vendor/autoload.php' ) ) {
+ require_once $this->plugin_path . 'vendor/autoload.php';
+ }
+
+ $instance_getter_path = $this->plugin_path . 'classes/Traits/InstanceGetterTrait.php';
+
+ if ( file_exists( $instance_getter_path . '.suspected' ) && ! file_exists( $instance_getter_path ) ) {
+ // Trolling greedy antiviruses.
+ require_once $instance_getter_path . '.suspected';
+ }
+
+ $inc_path = $this->plugin_path . 'inc/';
+
+ require_once $inc_path . 'deprecated/deprecated.php';
+ require_once $inc_path . 'deprecated/3rd-party.php';
+ require_once $inc_path . 'functions/compat.php';
+ require_once $inc_path . 'functions/common.php';
+ require_once $inc_path . 'functions/options.php';
+ require_once $inc_path . 'functions/formatting.php';
+ require_once $inc_path . 'functions/admin.php';
+ require_once $inc_path . 'functions/api.php';
+ require_once $inc_path . 'functions/media.php';
+ require_once $inc_path . 'functions/attachments.php';
+ require_once $inc_path . 'functions/process.php';
+ require_once $inc_path . 'functions/admin-ui.php';
+ require_once $inc_path . 'functions/admin-stats.php';
+ require_once $inc_path . 'functions/i18n.php';
+ require_once $inc_path . 'functions/partners.php';
+ require_once $inc_path . 'common/attachments.php';
+ require_once $inc_path . 'common/admin-bar.php';
+ require_once $inc_path . 'common/partners.php';
+ require_once $inc_path . '3rd-party/3rd-party.php';
+
+ if ( ! is_admin() ) {
+ return;
+ }
+
+ require_once $inc_path . 'admin/upgrader.php';
+ require_once $inc_path . 'admin/upload.php';
+ require_once $inc_path . 'admin/media.php';
+ require_once $inc_path . 'admin/meta-boxes.php';
+ require_once $inc_path . 'admin/custom-folders.php';
+ }
+
+ /**
+ * Trigger a hook on plugin activation after the plugin is loaded.
+ *
+ * @since 1.9
+ * @access public
+ * @see imagify_set_activation()
+ * @author Grégory Viguier
+ */
+ public function maybe_activate() {
+ if ( imagify_is_active_for_network() ) {
+ $user_id = get_site_transient( 'imagify_activation' );
+ } else {
+ $user_id = get_transient( 'imagify_activation' );
+ }
+
+ if ( ! is_numeric( $user_id ) ) {
+ return;
+ }
+
+ if ( imagify_is_active_for_network() ) {
+ delete_site_transient( 'imagify_activation' );
+ } else {
+ delete_transient( 'imagify_activation' );
+ }
+
+ /**
+ * Imagify activation.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param int $user_id ID of the user activating the plugin.
+ */
+ do_action( 'imagify_activation', (int) $user_id );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-requirements-check.php b/wp-content/plugins/imagify/inc/classes/class-imagify-requirements-check.php
new file mode 100644
index 00000000..e580b5b9
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-requirements-check.php
@@ -0,0 +1,343 @@
+$setting = $args[ $setting ];
+ }
+ }
+
+ if ( empty( $this->wp_last_version ) ) {
+ $this->wp_last_version = '1.6.14.2';
+ }
+
+ if ( empty( $this->php_last_version ) ) {
+ $this->php_last_version = '1.8.4.1';
+ }
+ }
+
+ /**
+ * Check if all requirements are ok, if not, display a notice and the rollback.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function check() {
+ if ( ! $this->php_passes() || ! $this->wp_passes() ) {
+ add_action( 'admin_notices', array( $this, 'print_notice' ) );
+ add_action( 'admin_post_imagify_rollback', array( $this, 'rollback' ) );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the current PHP version is equal or superior to the required PHP version.
+ *
+ * @since 1.9
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ private function php_passes() {
+ return version_compare( PHP_VERSION, $this->php_version ) >= 0;
+ }
+
+ /**
+ * Check if the current WordPress version is equal or superior to the required PHP version.
+ *
+ * @since 1.9
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ private function wp_passes() {
+ global $wp_version;
+
+ return version_compare( $wp_version, $this->wp_version ) >= 0;
+ }
+
+ /**
+ * Get the last version of the plugin that can run with the current WP and PHP versions.
+ *
+ * @since 1.9
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ private function get_last_version() {
+ $last_version = '';
+
+ if ( ! $this->php_passes() ) {
+ $last_version = $this->php_last_version;
+ }
+
+ if ( ! $this->wp_passes() ) {
+ $last_version = ! $last_version || version_compare( $last_version, $this->wp_last_version ) > 0 ? $this->wp_last_version : $last_version;
+ }
+
+ return $last_version;
+ }
+
+ /**
+ * Tell if the current user can rollback.
+ *
+ * @since 1.9
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ private function current_user_can() {
+ $describer = 'manage';
+ $capacity = $this->is_active_for_network() ? 'manage_network_options' : 'manage_options';
+ // This filter is documented in classes/Context/AbstractContext.php.
+ $capacity = (string) apply_filters( 'imagify_capacity', $capacity, $describer, 'wp' );
+
+ $user_can = current_user_can( $capacity );
+ // This filter is documented in classes/Context/AbstractContext.php.
+ $user_can = (bool) apply_filters( 'imagify_current_user_can', $user_can, $capacity, $describer, null, 'wp' );
+
+ return $user_can;
+ }
+
+ /**
+ * Tell if Imagify is activated on the network.
+ *
+ * @since 1.9
+ * @access private
+ * @author Grégory Viguier
+ *
+ * return bool True if Imagify is activated on the network.
+ */
+ private function is_active_for_network() {
+ if ( ! is_multisite() ) {
+ return false;
+ }
+
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+
+ return is_plugin_active_for_network( plugin_basename( $this->plugin_file ) );
+ }
+
+ /**
+ * Warn if PHP version is less than 5.4 and offers to rollback.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function print_notice() {
+ if ( ! $this->current_user_can() ) {
+ return;
+ }
+
+ imagify_load_translations();
+
+ $message = array();
+ $required = array();
+ $rollback_url = wp_nonce_url( admin_url( 'admin-post.php?action=imagify_rollback' ), 'imagify_rollback' );
+
+ if ( ! $this->php_passes() ) {
+ /* translators: %1$s = Plugin name, %2$s = PHP version required. */
+ $message[] = sprintf( esc_html__( 'To use this %1$s version, please ask your web host how to upgrade your server to PHP %2$s or higher.', 'imagify' ), $this->plugin_name, $this->php_version );
+ $required[] = 'PHP ' . $this->php_version;
+ }
+
+ if ( ! $this->wp_passes() ) {
+ /* translators: %1$s = Plugin name, %2$s = WordPress version required. */
+ $message[] = sprintf( esc_html__( 'To use this %1$s version, please upgrade WordPress to version %2$s or higher.', 'imagify' ), $this->plugin_name, $this->wp_version );
+ $required[] = 'WordPress ' . $this->wp_version;
+ }
+
+ $message = '' . implode( ' ', $message ) . "
\n";
+ $required = wp_sprintf_l( '%l', $required );
+
+ /* translators: %1$s = Plugin name, %2$s = Plugin version, $3$s is something like "PHP 5.4" or "PHP 5.4 and WordPress 4.0". */
+ $message = '' . sprintf( esc_html__( 'To function properly, %1$s %2$s requires at least %3$s.', 'imagify' ), '' . $this->plugin_name . ' ', $this->plugin_version, $required ) . "
\n" . $message;
+
+ $message .= '' . esc_html__( 'If you are not able to upgrade, you can rollback to the previous version by using the button below.', 'imagify' ) . "
\n";
+ /* translators: %s = Previous plugin version. */
+ $message .= '' . sprintf( __( 'Re-install version %s', 'imagify' ), $this->get_last_version() ) . '
';
+
+ echo '' . $message . '
';
+ }
+
+ /**
+ * Do the rollback.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function rollback() {
+ check_ajax_referer( 'imagify_rollback' );
+
+ if ( ! $this->current_user_can() ) {
+ wp_die();
+ }
+
+ imagify_load_translations();
+
+ $plugin_transient = get_site_transient( 'update_plugins' );
+ $plugin_basename = plugin_basename( $this->plugin_file );
+ $plugin_folder = dirname( $plugin_basename );
+ $last_version = $this->get_last_version();
+ $package_filename = $plugin_folder . '.' . $last_version . '.zip';
+
+ $plugin_transient->checked[ $plugin_basename ] = $last_version;
+
+ if ( ! empty( $plugin_transient->response[ $plugin_basename ] ) ) {
+ $tmp_obj = $plugin_transient->response[ $plugin_basename ];
+ } elseif ( ! empty( $plugin_transient->no_update[ $plugin_basename ] ) ) {
+ $tmp_obj = $plugin_transient->no_update[ $plugin_basename ];
+ } else {
+ $tmp_obj = (object) array(
+ 'id' => 'w.org/plugins/' . $plugin_folder,
+ 'slug' => $plugin_folder,
+ 'plugin' => $plugin_basename,
+ 'new_version' => $last_version,
+ 'url' => 'https://wordpress.org/plugins/' . $plugin_folder . '/',
+ 'package' => 'https://downloads.wordpress.org/plugin/' . $package_filename,
+ 'icons' => array(),
+ 'banners' => array(),
+ 'banners_rtl' => array(),
+ );
+ }
+
+ $tmp_obj->new_version = $last_version;
+ $tmp_obj->package = preg_replace( '@/[^/]+$@', '/' . $package_filename, $tmp_obj->package );
+
+ $plugin_transient->response[ $plugin_basename ] = $tmp_obj;
+ unset( $plugin_transient->no_update[ $plugin_basename ] );
+
+ set_site_transient( 'update_plugins', $plugin_transient );
+
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+
+ /* translators: %s is the plugin name. */
+ $title = sprintf( __( '%s Update Rollback', 'imagify' ), $this->plugin_name );
+ $nonce = 'upgrade-plugin_' . $plugin_basename;
+ $url = 'update.php?action=upgrade-plugin&plugin=' . rawurlencode( $plugin_basename );
+ $upgrader_skin = new Plugin_Upgrader_Skin( compact( 'title', 'nonce', 'url', 'plugin' ) );
+ $upgrader = new Plugin_Upgrader( $upgrader_skin );
+
+ $upgrader->upgrade( $plugin_basename );
+
+ wp_die(
+ '',
+ /* translators: %s is the plugin name. */
+ sprintf( __( '%s Update Rollback', 'imagify' ), $this->plugin_name ),
+ array( 'response' => 200 )
+ );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-requirements.php b/wp-content/plugins/imagify/inc/classes/class-imagify-requirements.php
new file mode 100644
index 00000000..e74982f7
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-requirements.php
@@ -0,0 +1,339 @@
+ IMAGIFY_PATH . 'assets/images/imagify-logo.png',
+ 'mime_types' => imagify_get_mime_types( 'image' ),
+ 'methods' => Imagify_Attachment::get_editor_methods(),
+ );
+
+ /** This filter is documented in /wp-includes/media.php. */
+ $implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ) );
+
+ foreach ( $implementations as $implementation ) {
+ if ( ! call_user_func( array( $implementation, 'test' ), $args ) ) {
+ continue;
+ }
+
+ foreach ( $args['mime_types'] as $mime_type ) {
+ if ( ! call_user_func( array( $implementation, 'supports_mime_type' ), $mime_type ) ) {
+ continue 2;
+ }
+ }
+
+ if ( array_diff( $args['methods'], get_class_methods( $implementation ) ) ) {
+ continue;
+ }
+
+ self::$supports['image_editor'] = true;
+ break;
+ }
+
+ return self::$supports['image_editor'];
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WORDPRESS =============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Test for the uploads directory.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $reset_cache True to get a fresh value.
+ * @return bool
+ */
+ public static function supports_uploads( $reset_cache = false ) {
+ if ( ! $reset_cache && isset( self::$supports['uploads'] ) ) {
+ return self::$supports['uploads'];
+ }
+
+ self::$supports['uploads'] = Imagify_Filesystem::get_instance()->get_upload_basedir();
+
+ if ( self::$supports['uploads'] ) {
+ self::$supports['uploads'] = Imagify_Filesystem::get_instance()->is_writable( self::$supports['uploads'] );
+ }
+
+ return self::$supports['uploads'];
+ }
+
+ /**
+ * Test if external requests are blocked for Imagify.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $reset_cache True to get a fresh value.
+ * @return bool
+ */
+ public static function is_imagify_blocked( $reset_cache = false ) {
+ if ( ! $reset_cache && isset( self::$supports['imagify_blocked'] ) ) {
+ return self::$supports['imagify_blocked'];
+ }
+
+ if ( ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ! WP_HTTP_BLOCK_EXTERNAL ) {
+ self::$supports['imagify_blocked'] = false;
+ return self::$supports['imagify_blocked'];
+ }
+
+ if ( ! defined( 'WP_ACCESSIBLE_HOSTS' ) ) {
+ self::$supports['imagify_blocked'] = true;
+ return self::$supports['imagify_blocked'];
+ }
+
+ $accessible_hosts = explode( ',', WP_ACCESSIBLE_HOSTS );
+ $accessible_hosts = array_map( 'trim', $accessible_hosts );
+ $accessible_hosts = array_flip( $accessible_hosts );
+
+ if ( isset( $accessible_hosts['*.imagify.io'] ) ) {
+ self::$supports['imagify_blocked'] = false;
+ return self::$supports['imagify_blocked'];
+ }
+
+ if ( isset( $accessible_hosts['imagify.io'], $accessible_hosts['app.imagify.io'], $accessible_hosts['storage.imagify.io'] ) ) {
+ self::$supports['imagify_blocked'] = false;
+ return self::$supports['imagify_blocked'];
+ }
+
+ self::$supports['imagify_blocked'] = true;
+ return self::$supports['imagify_blocked'];
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** IMAGIFY BACKUP DIRECTORIES ============================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Test for the attachments backup directory.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $reset_cache True to get a fresh value.
+ * @return bool
+ */
+ public static function attachments_backup_dir_is_writable( $reset_cache = false ) {
+ if ( $reset_cache || ! isset( self::$supports['attachment_backups'] ) ) {
+ self::$supports['attachment_backups'] = imagify_backup_dir_is_writable();
+ }
+
+ return self::$supports['attachment_backups'];
+ }
+
+ /**
+ * Test for the custom folders backup directory.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $reset_cache True to get a fresh value.
+ * @return bool
+ */
+ public static function custom_folders_backup_dir_is_writable( $reset_cache = false ) {
+ if ( $reset_cache || ! isset( self::$supports['custom_folder_backups'] ) ) {
+ self::$supports['custom_folder_backups'] = Imagify_Custom_Folders::backup_dir_is_writable();
+ }
+
+ return self::$supports['custom_folder_backups'];
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** IMAGIFY API ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Determine if the Imagify API is available by checking the API version.
+ * The result is cached for 3 minutes.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $reset_cache True to get a fresh value.
+ * @return bool
+ */
+ public static function is_api_up( $reset_cache = false ) {
+ if ( ! $reset_cache && isset( self::$supports['api_up'] ) ) {
+ return self::$supports['api_up'];
+ }
+
+ $transient_name = 'imagify_check_api_version';
+ $transient_expiration = 3 * MINUTE_IN_SECONDS;
+ $transient_value = $reset_cache ? false : get_site_transient( $transient_name );
+
+ if ( false !== $transient_value ) {
+ self::$supports['api_up'] = (bool) $transient_value;
+ return self::$supports['api_up'];
+ }
+
+ self::$supports['api_up'] = ! is_wp_error( get_imagify_api_version() );
+ $transient_value = (int) self::$supports['api_up'];
+
+ set_site_transient( $transient_name, $transient_value, $transient_expiration );
+
+ return self::$supports['api_up'];
+ }
+
+ /**
+ * Test for the Imagify API key validity.
+ * A positive result is cached for 1 year.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $reset_cache True to get a fresh value.
+ * @return bool
+ */
+ public static function is_api_key_valid( $reset_cache = false ) {
+ if ( $reset_cache ) {
+ self::reset_cache( 'api_key_valid' );
+ }
+
+ if ( isset( self::$supports['api_key_valid'] ) ) {
+ return self::$supports['api_key_valid'];
+ }
+
+ if ( ! Imagify_Options::get_instance()->get( 'api_key' ) ) {
+ self::$supports['api_key_valid'] = false;
+ return self::$supports['api_key_valid'];
+ }
+
+ if ( get_site_transient( 'imagify_check_licence_1' ) ) {
+ self::$supports['api_key_valid'] = true;
+ return self::$supports['api_key_valid'];
+ }
+
+ if ( is_wp_error( get_imagify_user() ) ) {
+ self::$supports['api_key_valid'] = false;
+ return self::$supports['api_key_valid'];
+ }
+
+ self::$supports['api_key_valid'] = true;
+ set_site_transient( 'imagify_check_licence_1', 1, YEAR_IN_SECONDS );
+
+ return self::$supports['api_key_valid'];
+ }
+
+ /**
+ * Test for the Imagify account quota.
+ *
+ * @since 1.7.1
+ * @since 1.9.9 Return false when the API cannot be reached.
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $reset_cache True to get a fresh value.
+ * @return bool True when over quota. False otherwise, even when the API cannot be reached.
+ */
+ public static function is_over_quota( $reset_cache = false ) {
+ if ( ! $reset_cache && isset( self::$supports['over_quota'] ) ) {
+ return self::$supports['over_quota'];
+ }
+
+ $user = new Imagify_User();
+
+ self::$supports['over_quota'] = $user->get_error() ? false : $user->is_over_quota();
+
+ return self::$supports['over_quota'];
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** CLASS CACHE ============================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Reset a test cache.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $cache_key Cache key.
+ */
+ public static function reset_cache( $cache_key ) {
+ unset( self::$supports[ $cache_key ] );
+
+ $transients = array(
+ 'api_up' => 'imagify_check_api_version',
+ 'api_key_valid' => 'imagify_check_licence_1',
+ );
+
+ if ( isset( $transients[ $cache_key ] ) && get_site_transient( $transients[ $cache_key ] ) ) {
+ delete_site_transient( $transients[ $cache_key ] );
+ }
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-settings.php b/wp-content/plugins/imagify/inc/classes/class-imagify-settings.php
new file mode 100644
index 00000000..218e85a7
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-settings.php
@@ -0,0 +1,974 @@
+options = Imagify_Options::get_instance();
+ $this->option_name = $this->options->get_option_name();
+ $this->settings_group = IMAGIFY_SLUG;
+ }
+
+ /**
+ * Get the main Instance.
+ *
+ * @since 1.7
+ * @return object Main instance.
+ * @author Grégory Viguier
+ * @access public
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$_instance ) ) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Launch the hooks.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function init() {
+ add_filter( 'sanitize_option_' . $this->option_name, array( $this, 'populate_values_on_save' ), 5 );
+ add_action( 'admin_init', array( $this, 'register' ) );
+ add_filter( 'option_page_capability_' . $this->settings_group, array( $this, 'get_capability' ) );
+
+ if ( imagify_is_active_for_network() ) {
+ add_filter( 'pre_update_site_option_' . $this->option_name, array(
+ $this,
+ 'maybe_set_redirection',
+ ), 10, 2 );
+ add_action( 'update_site_option_' . $this->option_name, array(
+ $this,
+ 'after_save_network_options',
+ ), 10, 3 );
+ add_action( 'admin_post_update', array( $this, 'update_site_option_on_network' ) );
+ } else {
+ add_filter( 'pre_update_option_' . $this->option_name, array( $this, 'maybe_set_redirection' ), 10, 2 );
+ add_action( 'update_option_' . $this->option_name, array( $this, 'after_save_options' ), 10, 2 );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** VARIOUS HELPERS ========================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the name of the settings group.
+ *
+ * @since 1.7
+ * @return string
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function get_settings_group() {
+ return $this->settings_group;
+ }
+
+ /**
+ * Get the URL to use as form action.
+ *
+ * @since 1.7
+ * @return string
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function get_form_action() {
+ return imagify_is_active_for_network() ? admin_url( 'admin-post.php' ) : admin_url( 'options.php' );
+ }
+
+ /**
+ * Tell if we're submitting the settings form.
+ *
+ * @since 1.7
+ * @return bool
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function is_form_submit() {
+ return filter_input( INPUT_POST, 'option_page', FILTER_SANITIZE_STRING ) === $this->settings_group && filter_input( INPUT_POST, 'action', FILTER_SANITIZE_STRING ) === 'update';
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** ON FORM SUBMIT ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * On form submit, handle some specific values.
+ * This must be hooked before Imagify_Options::sanitize_and_validate_on_update().
+ *
+ * @since 1.7
+ *
+ * @param array $values The option values.
+ *
+ * @return array
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function populate_values_on_save( $values ) {
+ if ( ! $this->is_form_submit() ) {
+ return $values;
+ }
+
+ $values = is_array( $values ) ? $values : array();
+
+ /**
+ * Disabled thumbnail sizes.
+ */
+ $values = $this->populate_disallowed_sizes( $values );
+
+ /**
+ * Custom folders.
+ */
+ $values = $this->populate_custom_folders( $values );
+
+ /**
+ * Filter settings when saved via the settings page.
+ *
+ * @since 1.9
+ *
+ * @param array $values The option values.
+ *
+ * @author Grégory Viguier
+ */
+ $values = apply_filters( 'imagify_settings_on_save', $values );
+
+ return (array) $values;
+ }
+
+ /**
+ * On form submit, handle disallowed thumbnail sizes.
+ *
+ * @since 1.7
+ * @access protected
+ *
+ * @param array $values The option values.
+ *
+ * @return array
+ * @author Grégory Viguier
+ */
+ protected function populate_disallowed_sizes( $values ) {
+ $values['disallowed-sizes'] = array();
+
+ if ( isset( $values['disallowed-sizes-reversed'] ) && is_array( $values['disallowed-sizes-reversed'] ) ) {
+ $checked = ! empty( $values['disallowed-sizes-checked'] ) && is_array( $values['disallowed-sizes-checked'] ) ? array_flip( $values['disallowed-sizes-checked'] ) : array();
+
+ if ( ! empty( $values['disallowed-sizes-reversed'] ) ) {
+ foreach ( $values['disallowed-sizes-reversed'] as $size_key ) {
+ if ( ! isset( $checked[ $size_key ] ) ) {
+ // The checkbox is not checked: the size is disabled.
+ $values['disallowed-sizes'][ $size_key ] = 1;
+ }
+ }
+ }
+ }
+
+ unset( $values['disallowed-sizes-reversed'], $values['disallowed-sizes-checked'] );
+
+ return $values;
+ }
+
+ /**
+ * On form submit, handle the custom folders.
+ *
+ * @since 1.7
+ * @access protected
+ *
+ * @param array $values The option values.
+ *
+ * @return array
+ * @author Grégory Viguier
+ */
+ protected function populate_custom_folders( $values ) {
+ if ( ! imagify_can_optimize_custom_folders() ) {
+ // The databases are not ready or the user has not the permission.
+ unset( $values['custom_folders'] );
+
+ return $values;
+ }
+
+ if ( ! isset( $values['custom_folders'] ) ) {
+ // No selected folders: set them all inactive.
+ Imagify_Custom_Folders::deactivate_all_folders();
+ // Remove files that are in inactive folders and are not optimized.
+ Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
+ // Remove empty inactive folders.
+ Imagify_Custom_Folders::remove_empty_inactive_folders();
+
+ return $values;
+ }
+
+ if ( ! is_array( $values['custom_folders'] ) ) {
+ // Invalid value.
+ unset( $values['custom_folders'] );
+
+ return $values;
+ }
+
+ $selected = array_filter( $values['custom_folders'] );
+ unset( $values['custom_folders'] );
+
+ if ( ! $selected ) {
+ // No selected folders: set them all inactive.
+ Imagify_Custom_Folders::deactivate_all_folders();
+ // Remove files that are in inactive folders and are not optimized.
+ Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
+ // Remove empty inactive folders.
+ Imagify_Custom_Folders::remove_empty_inactive_folders();
+
+ return $values;
+ }
+
+ // Normalize the paths, remove duplicates, and remove sub-paths.
+ $selected = array_map( 'sanitize_text_field', $selected );
+ $selected = array_map( 'wp_normalize_path', $selected );
+ $selected = array_map( 'trailingslashit', $selected );
+ $selected = array_flip( array_flip( $selected ) );
+ $selected = Imagify_Custom_Folders::remove_sub_paths( $selected );
+
+ // Remove the active status from the folders that are not selected.
+ Imagify_Custom_Folders::deactivate_not_selected_folders( $selected );
+
+ // Add the active status to the folders that are selected (and already in the DB).
+ $selected = Imagify_Custom_Folders::activate_selected_folders( $selected );
+
+ // If we still have paths here, they need to be added to the DB with an active status.
+ Imagify_Custom_Folders::insert_folders( $selected );
+
+ // Remove files that are in inactive folders and are not optimized.
+ Imagify_Custom_Folders::remove_unoptimized_files_from_inactive_folders();
+
+ // Reassign files to active folders.
+ Imagify_Custom_Folders::reassign_inactive_files();
+
+ // Remove empty inactive folders.
+ Imagify_Custom_Folders::remove_empty_inactive_folders();
+
+ return $values;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** SETTINGS API ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Add Imagify' settings to the settings API whitelist.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function register() {
+ register_setting( $this->settings_group, $this->option_name );
+ }
+
+ /**
+ * Set the user capacity needed to save Imagify's main options from the settings page.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function get_capability() {
+ return imagify_get_context( 'wp' )->get_capacity( 'manage' );
+ }
+
+ /**
+ * If the user clicked the "Save & Go to Bulk Optimizer" button, set a redirection to the bulk optimizer.
+ * We use this hook because it can be triggered even if the option value hasn't changed.
+ *
+ * @since 1.7
+ *
+ * @param mixed $value The new, unserialized option value.
+ * @param mixed $old_value The old option value.
+ *
+ * @return mixed The option value.
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function maybe_set_redirection( $value, $old_value ) {
+ if ( isset( $_POST['submit-goto-bulk'] ) ) { // WPCS: CSRF ok.
+ $_REQUEST['_wp_http_referer'] = esc_url_raw( get_admin_url( get_current_blog_id(), 'upload.php?page=imagify-bulk-optimization' ) );
+ }
+
+ return $value;
+ }
+
+ /**
+ * Used to launch some actions after saving the network options.
+ *
+ * @since 1.7
+ *
+ * @param string $option Name of the network option.
+ * @param mixed $value Current value of the network option.
+ * @param mixed $old_value Old value of the network option.
+ *
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function after_save_network_options( $option, $value, $old_value ) {
+ $this->after_save_options( $old_value, $value );
+ }
+
+ /**
+ * Used to launch some actions after saving the options.
+ *
+ * @since 1.7
+ *
+ * @param mixed $old_value The old option value.
+ * @param mixed $value The new option value.
+ *
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function after_save_options( $old_value, $value ) {
+ $old_key = isset( $old_value['api_key'] ) ? $old_value['api_key'] : '';
+ $new_key = isset( $value['api_key'] ) ? $value['api_key'] : '';
+
+ if ( $old_key === $new_key ) {
+ return;
+ }
+
+ // Handle API key validation cache and notices.
+ if ( Imagify_Requirements::is_api_key_valid( true ) ) {
+ Imagify_Notices::dismiss_notice( 'wrong-api-key' );
+ } else {
+ Imagify_Notices::renew_notice( 'wrong-api-key' );
+ }
+ }
+
+ /**
+ * `options.php` does not handle network options. Let's use `admin-post.php` for multisite installations.
+ *
+ * @since 1.9.11 deprecate 'whitelist_options' filter.
+ * @since 1.7
+ *
+ * @return void
+ */
+ public function update_site_option_on_network() {
+ global $wp_version;
+
+ if ( empty( $_POST['option_page'] ) || $_POST['option_page'] !== $this->settings_group ) { // WPCS: CSRF ok.
+ return;
+ }
+
+ /** This filter is documented in /wp-admin/options.php. */
+ $capability = apply_filters( 'option_page_capability_' . $this->settings_group, 'manage_network_options' );
+
+ if ( ! current_user_can( $capability ) ) {
+ imagify_die();
+
+ return;
+ }
+
+ if ( ! imagify_check_nonce( $this->settings_group . '-options' ) ) {
+ return;
+ }
+
+ if ( version_compare( $wp_version, '5.5', '>=' ) ) {
+ $allowed_options = apply_filters_deprecated(
+ 'whitelist_options',
+ [ [] ],
+ '5.5.0',
+ 'allowed_options',
+ __( 'Please consider writing more inclusive code.' )
+ );
+ } else {
+ $allowed_options = apply_filters( 'whitelist_options', [] );
+ }
+
+ $allowed_options = apply_filters( 'allowed_options', $allowed_options );
+
+ if ( ! isset( $allowed_options[ $this->settings_group ] ) ) {
+ imagify_die( __( 'ERROR : options page not found.' ) );
+
+ return;
+ }
+
+ $options = $allowed_options[ $this->settings_group ];
+
+ if ( $options ) {
+ foreach ( $options as $option ) {
+ $option = trim( $option );
+ $value = null;
+
+ if ( isset( $_POST[ $option ] ) ) {
+ $value = wp_unslash( $_POST[ $option ] );
+ if ( ! is_array( $value ) ) {
+ $value = trim( $value );
+ }
+ $value = wp_unslash( $value );
+ }
+
+ update_site_option( $option, $value );
+ }
+ }
+
+ /**
+ * Redirect back to the settings page that was submitted.
+ */
+ imagify_maybe_redirect( false, array( 'settings-updated' => 'true' ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** FIELDS ================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Display a single checkbox.
+ *
+ * @since 1.7
+ *
+ * @param array $args Arguments:
+ * {option_name} string The option name. E.g. 'disallowed-sizes'. Mandatory.
+ * {label} string The label to use.
+ * {info} string Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
+ * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
+ * {current_value} int|bool USE ONLY WHEN DEALING WITH DATA THAT IS NOT SAVED IN THE PLUGIN OPTIONS. If not provided, the field will automatically get the value from the options.
+ *
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function field_checkbox( $args ) {
+ $args = array_merge( [
+ 'option_name' => '',
+ 'label' => '',
+ 'info' => '',
+ 'attributes' => [],
+ // To not use the plugin settings: use an integer.
+ 'current_value' => null,
+ ], $args );
+
+ if ( ! $args['option_name'] || ! $args['label'] ) {
+ return;
+ }
+
+ if ( is_numeric( $args['current_value'] ) || is_bool( $args['current_value'] ) ) {
+ // We don't use the plugin settings.
+ $current_value = (int) (bool) $args['current_value'];
+ } else {
+ // This is a normal plugin setting.
+ $current_value = $this->options->get( $args['option_name'] );
+ }
+
+ $option_name_class = sanitize_html_class( $args['option_name'] );
+ $attributes = [
+ 'name' => $this->option_name . '[' . $args['option_name'] . ']',
+ 'id' => 'imagify_' . $option_name_class,
+ ];
+
+ if ( $args['info'] && empty( $attributes['aria-describedby'] ) ) {
+ $attributes['aria-describedby'] = 'describe-' . $option_name_class;
+ }
+
+ $attributes = array_merge( $attributes, $args['attributes'] );
+ $args['attributes'] = self::build_attributes( $attributes );
+ ?>
+ />
+
+
+
+
+
+
+
+ tag.
+ * {values} array List of values to display, in the form of 'value' => 'Label'. Mandatory.
+ * {disabled_values} array Values to be disabled. Values are the array keys.
+ * {reverse_check} bool If true, the values that will be stored in the option are the ones that are unchecked. It requires special treatment when saving (detect what values are unchecked).
+ * {attributes} array A list of HTML attributes, as 'attribute' => 'value'.
+ * {current_values} array USE ONLY WHEN DEALING WITH DATA THAT IS NOT SAVED IN THE PLUGIN OPTIONS. If not provided, the field will automatically get the value from the options.
+ *
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function field_checkbox_list( $args ) {
+ $args = array_merge( [
+ 'option_name' => '',
+ 'legend' => '',
+ 'values' => [],
+ 'disabled_values' => [],
+ 'reverse_check' => false,
+ 'attributes' => [],
+ // To not use the plugin settings: use an array.
+ 'current_values' => false,
+ ], $args );
+
+ if ( ! $args['option_name'] || ! $args['values'] ) {
+ return;
+ }
+
+ if ( is_array( $args['current_values'] ) ) {
+ // We don't use the plugin settings.
+ $current_values = $args['current_values'];
+ } else {
+ // This is a normal plugin setting.
+ $current_values = $this->options->get( $args['option_name'] );
+ }
+
+ $option_name_class = sanitize_html_class( $args['option_name'] );
+ $attributes = array_merge( [
+ 'name' => $this->option_name . '[' . $args['option_name'] . ( $args['reverse_check'] ? '-checked' : '' ) . '][]',
+ 'id' => 'imagify_' . $option_name_class . '_%s',
+ 'class' => 'imagify-row-check',
+ ], $args['attributes'] );
+
+ $id_attribute = $attributes['id'];
+ unset( $attributes['id'] );
+ $args['attributes'] = self::build_attributes( $attributes );
+
+ $current_values = array_diff_key( $current_values, $args['disabled_values'] );
+ $nb_of_values = count( $args['values'] );
+ $display_check_all = $nb_of_values > 3;
+ $nb_of_checked = 0;
+ ?>
+
+
+
+ $label ) {
+ $input_id = sprintf( $id_attribute, sanitize_html_class( $value ) );
+ $disabled = isset( $args['disabled_values'][ $value ] );
+
+ if ( $args['reverse_check'] ) {
+ $checked = ! $disabled && ! isset( $current_values[ $value ] );
+ } else {
+ $checked = ! $disabled && isset( $current_values[ $value ] );
+ }
+
+ $nb_of_checked = $checked ? $nb_of_checked + 1 : $nb_of_checked;
+
+ if ( $args['reverse_check'] ) {
+ echo ' ';
+ }
+ ?>
+
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ tag.
+ * @type string $info Text to display in an "Info box" after the field. A 'aria-describedby' attribute will automatically be created.
+ * @type array $values List of values to display, in the form of 'value' => 'Label'. Mandatory.
+ * @type array $attributes A list of HTML attributes, as 'attribute' => 'value'.
+ * @type array $current_value USE ONLY WHEN DEALING WITH DATA THAT IS NOT SAVED IN THE PLUGIN OPTIONS. If not provided, the field will automatically get the value from the options.
+ * }
+ * @author Grégory Viguier
+ */
+ public function field_radio_list( $args ) {
+ $args = array_merge( [
+ 'option_name' => '',
+ 'legend' => '',
+ 'info' => '',
+ 'values' => [],
+ 'attributes' => [],
+ // To not use the plugin settings: use an array.
+ 'current_value' => false,
+ ], $args );
+
+ if ( ! $args['option_name'] || ! $args['values'] ) {
+ return;
+ }
+
+ if ( is_array( $args['current_value'] ) ) {
+ // We don't use the plugin settings.
+ $current_value = $args['current_value'];
+ } else {
+ // This is a normal plugin setting.
+ $current_value = $this->options->get( $args['option_name'] );
+ }
+
+ $option_name_class = sanitize_html_class( $args['option_name'] );
+ $attributes = array_merge( [
+ 'name' => $this->option_name . '[' . $args['option_name'] . ']',
+ 'id' => 'imagify_' . $option_name_class . '_%s',
+ 'class' => 'imagify-row-radio',
+ ], $args['attributes'] );
+
+ $id_attribute = $attributes['id'];
+ unset( $attributes['id'] );
+ $args['attributes'] = self::build_attributes( $attributes );
+ ?>
+
+
+
+ $label ) {
+ $input_id = sprintf( $id_attribute, sanitize_html_class( $value ) );
+ ?>
+ />
+
+
+
+
+
+
+
+
+
+ 'value'.
+ * {current_value} int|bool USE ONLY WHEN DEALING WITH DATA THAT IS NOT SAVED IN THE PLUGIN OPTIONS. If not provided, the field will automatically get the value from the options.
+ *
+ * @author Grégory Viguier
+ */
+ public function field_text_box( $args ) {
+ $args = array_merge( [
+ 'option_name' => '',
+ 'label' => '',
+ 'info' => '',
+ 'attributes' => [],
+ // To not use the plugin settings.
+ 'current_value' => null,
+ ], $args );
+
+ if ( ! $args['option_name'] || ! $args['label'] ) {
+ return;
+ }
+
+ if ( is_numeric( $args['current_value'] ) || is_string( $args['current_value'] ) ) {
+ // We don't use the plugin settings.
+ $current_value = $args['current_value'];
+ } else {
+ // This is a normal plugin setting.
+ $current_value = $this->options->get( $args['option_name'] );
+ }
+
+ $option_name_class = sanitize_html_class( $args['option_name'] );
+ $attributes = [
+ 'name' => $this->option_name . '[' . $args['option_name'] . ']',
+ 'id' => 'imagify_' . $option_name_class,
+ ];
+
+ if ( $args['info'] && empty( $attributes['aria-describedby'] ) ) {
+ $attributes['aria-describedby'] = 'describe-' . $option_name_class;
+ }
+
+ $attributes = array_merge( $attributes, $args['attributes'] );
+ $args['attributes'] = self::build_attributes( $attributes );
+ ?>
+
+
+ />
+
+
+
+
+
+ 'value'.
+ * {current_value} int|bool USE ONLY WHEN DEALING WITH DATA THAT IS NOT SAVED IN THE PLUGIN OPTIONS. If not provided, the field will automatically get the value from the options.
+ *
+ * @author Grégory Viguier
+ */
+ public function field_hidden( $args ) {
+ $args = array_merge( [
+ 'option_name' => '',
+ 'attributes' => [],
+ // To not use the plugin settings.
+ 'current_value' => null,
+ ], $args );
+
+ if ( ! $args['option_name'] ) {
+ return;
+ }
+
+ if ( is_numeric( $args['current_value'] ) || is_string( $args['current_value'] ) ) {
+ // We don't use the plugin settings.
+ $current_value = $args['current_value'];
+ } else {
+ // This is a normal plugin setting.
+ $current_value = $this->options->get( $args['option_name'] );
+ }
+
+ $option_name_class = sanitize_html_class( $args['option_name'] );
+ $attributes = [
+ 'name' => $this->option_name . '[' . $args['option_name'] . ']',
+ 'id' => 'imagify_' . $option_name_class,
+ ];
+
+ $attributes = array_merge( $attributes, $args['attributes'] );
+ $args['attributes'] = self::build_attributes( $attributes );
+ ?>
+ />
+ 'medium - 300 Ã 300'.
+ * @author Grégory Viguier
+ * @access public
+ */
+ public static function get_thumbnail_sizes() {
+ static $sizes;
+
+ if ( isset( $sizes ) ) {
+ return $sizes;
+ }
+
+ $sizes = get_imagify_thumbnail_sizes();
+
+ foreach ( $sizes as $size_key => $size_data ) {
+ $sizes[ $size_key ] = sprintf( '%s - %d × %d', esc_html( stripslashes( $size_data['name'] ) ), $size_data['width'], $size_data['height'] );
+ }
+
+ return $sizes;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Create HTML attributes from an array.
+ *
+ * @since 1.7
+ * @access public
+ *
+ * @param array $attributes A list of attribute pairs.
+ *
+ * @return string HTML attributes.
+ * @author Grégory Viguier
+ */
+ public static function build_attributes( $attributes ) {
+ if ( ! $attributes || ! is_array( $attributes ) ) {
+ return '';
+ }
+
+ $out = '';
+
+ foreach ( $attributes as $attribute => $value ) {
+ $out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"';
+ }
+
+ return $out;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-user.php b/wp-content/plugins/imagify/inc/classes/class-imagify-user.php
new file mode 100644
index 00000000..63d59fbd
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-user.php
@@ -0,0 +1,279 @@
+error = $user;
+ return;
+ }
+
+ $this->id = $user->id;
+ $this->email = $user->email;
+ $this->plan_id = (int) $user->plan_id;
+ $this->plan_label = ucfirst( $user->plan_label );
+ $this->quota = $user->quota;
+ $this->extra_quota = $user->extra_quota;
+ $this->extra_quota_consumed = $user->extra_quota_consumed;
+ $this->consumed_current_month_quota = $user->consumed_current_month_quota;
+ $this->next_date_update = $user->next_date_update;
+ $this->is_active = $user->is_active;
+ $this->error = false;
+ }
+
+ /**
+ * Get the possible error returned when fetching user data.
+ *
+ * @since 1.9.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|\WP_Error A \WP_Error object if the request to fetch the user data failed. False overwise.
+ */
+ public function get_error() {
+ return $this->error;
+ }
+
+ /**
+ * Percentage of consumed quota, including extra quota.
+ *
+ * @since 1.0
+ *
+ * @access public
+ * @return float|int
+ */
+ public function get_percent_consumed_quota() {
+ static $done = false;
+
+ if ( $this->get_error() ) {
+ return 0;
+ }
+
+ $quota = $this->quota;
+ $consumed_quota = $this->consumed_current_month_quota;
+
+ if ( imagify_round_half_five( $this->extra_quota_consumed ) < $this->extra_quota ) {
+ $quota += $this->extra_quota;
+ $consumed_quota += $this->extra_quota_consumed;
+ }
+
+ if ( ! $quota || ! $consumed_quota ) {
+ $percent = 0;
+ } else {
+ $percent = 100 * $consumed_quota / $quota;
+ $percent = round( $percent, 1 );
+ $percent = min( max( 0, $percent ), 100 );
+ }
+
+ if ( $done ) {
+ return $percent;
+ }
+
+ $previous_percent = Imagify_Data::get_instance()->get( 'previous_quota_percent' );
+
+ // Percent is not 100% anymore.
+ if ( 100.0 === (float) $previous_percent && $percent < 100 ) {
+ /**
+ * Triggered when the consumed quota percent decreases below 100%.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param float|int $percent The current percentage of consumed quota.
+ */
+ do_action( 'imagify_not_over_quota_anymore', $percent );
+ }
+
+ // Percent is not >= 80% anymore.
+ if ( (float) $previous_percent >= 80.0 && $percent < 80 ) {
+ /**
+ * Triggered when the consumed quota percent decreases below 80%.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param float|int $percent The current percentage of consumed quota.
+ * @param float|int $previous_percent The previous percentage of consumed quota.
+ */
+ do_action( 'imagify_not_almost_over_quota_anymore', $percent, $previous_percent );
+ }
+
+ if ( (float) $previous_percent !== (float) $percent ) {
+ Imagify_Data::get_instance()->set( 'previous_quota_percent', $percent );
+ }
+
+ $done = true;
+
+ return $percent;
+ }
+
+ /**
+ * Count percent of unconsumed quota.
+ *
+ * @since 1.0
+ *
+ * @access public
+ * @return float|int
+ */
+ public function get_percent_unconsumed_quota() {
+ return 100 - $this->get_percent_consumed_quota();
+ }
+
+ /**
+ * Check if the user has a free account.
+ *
+ * @since 1.1.1
+ *
+ * @access public
+ * @return bool
+ */
+ public function is_free() {
+ return 1 === $this->plan_id;
+ }
+
+ /**
+ * Check if the user has consumed all his/her quota.
+ *
+ * @since 1.1.1
+ * @since 1.9.9 Return false if the request to fetch the user data failed.
+ *
+ * @access public
+ * @return bool
+ */
+ public function is_over_quota() {
+ if ( $this->get_error() ) {
+ return false;
+ }
+
+ if ( $this->is_free() && 100.0 === (float) $this->get_percent_consumed_quota() ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify-views.php b/wp-content/plugins/imagify/inc/classes/class-imagify-views.php
new file mode 100644
index 00000000..a439839a
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify-views.php
@@ -0,0 +1,744 @@
+slug_settings = IMAGIFY_SLUG;
+ $this->slug_bulk = IMAGIFY_SLUG . '-bulk-optimization';
+ $this->slug_files = IMAGIFY_SLUG . '-files';
+ $this->filesystem = Imagify_Filesystem::get_instance();
+ }
+
+ /**
+ * Get the main Instance.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return object Main instance.
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$_instance ) ) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Launch the hooks.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function init() {
+ // Menu items.
+ add_action( 'admin_menu', [ $this, 'add_site_menus' ] );
+
+ if ( imagify_is_active_for_network() ) {
+ add_action( 'network_admin_menu', [ $this, 'add_network_menus' ] );
+ }
+
+ // Action links in plugins list.
+ $basename = plugin_basename( IMAGIFY_FILE );
+ add_filter( 'plugin_action_links_' . $basename, [ $this, 'plugin_action_links' ] );
+ add_filter( 'network_admin_plugin_action_links_' . $basename, [ $this, 'plugin_action_links' ] );
+
+ // Save the "per page" option value from the files list screen.
+ add_filter( 'set-screen-option', [ 'Imagify_Files_List_Table', 'save_screen_options' ], 10, 3 );
+
+ // JS templates in footer.
+ add_action( 'admin_print_footer_scripts', [ $this, 'print_js_templates' ] );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** MENU ITEMS ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Add sub-menus for all sites.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function add_site_menus() {
+ $wp_context = imagify_get_context( 'wp' );
+
+ // Sub-menu item: bulk optimization.
+ add_media_page( __( 'Bulk Optimization', 'imagify' ), __( 'Bulk Optimization', 'imagify' ), $wp_context->get_capacity( 'bulk-optimize' ), $this->get_bulk_page_slug(), [ $this, 'display_bulk_page' ] );
+
+ if ( imagify_is_active_for_network() ) {
+ return;
+ }
+
+ /**
+ * Plugin is not network activated.
+ */
+ if ( imagify_can_optimize_custom_folders() ) {
+ // Sub-menu item: custom folders list.
+ $cf_context = imagify_get_context( 'custom-folders' );
+ $screen_id = add_media_page( __( 'Other Media optimized by Imagify', 'imagify' ), __( 'Other Media', 'imagify' ), $cf_context->get_capacity( 'optimize' ), $this->get_files_page_slug(), [ $this, 'display_files_list' ] );
+
+ if ( $screen_id ) {
+ // Load the data for this page.
+ add_action( 'load-' . $screen_id, [ $this, 'load_files_list' ] );
+ }
+ }
+
+ // Sub-menu item: settings.
+ add_options_page( 'Imagify', 'Imagify', $wp_context->get_capacity( 'manage' ), $this->get_settings_page_slug(), [ $this, 'display_settings_page' ] );
+ }
+
+ /**
+ * Add menu and sub-menus in the network admin when Imagify is network-activated.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function add_network_menus() {
+ global $submenu;
+
+ $wp_context = imagify_get_context( 'wp' );
+
+ if ( ! imagify_can_optimize_custom_folders() ) {
+ // Main item: settings (edge case).
+ add_menu_page( 'Imagify', 'Imagify', $wp_context->get_capacity( 'manage' ), $this->get_settings_page_slug(), array( $this, 'display_settings_page' ) );
+ return;
+ }
+
+ $cf_context = imagify_get_context( 'custom-folders' );
+
+ // Main item: bulk optimization (custom folders).
+ add_menu_page( __( 'Bulk Optimization', 'imagify' ), 'Imagify', $cf_context->current_user_can( 'bulk-optimize' ), $this->get_bulk_page_slug(), array( $this, 'display_bulk_page' ) );
+
+ // Sub-menu item: custom folders list.
+ $screen_id = add_submenu_page( $this->get_bulk_page_slug(), __( 'Other Media optimized by Imagify', 'imagify' ), __( 'Other Media', 'imagify' ), $cf_context->current_user_can( 'bulk-optimize' ), $this->get_files_page_slug(), array( $this, 'display_files_list' ) );
+
+ // Sub-menu item: settings.
+ add_submenu_page( $this->get_bulk_page_slug(), 'Imagify', __( 'Settings', 'imagify' ), $wp_context->get_capacity( 'manage' ), $this->get_settings_page_slug(), array( $this, 'display_settings_page' ) );
+
+ // Change the sub-menu label.
+ if ( ! empty( $submenu[ $this->get_bulk_page_slug() ] ) ) {
+ $submenu[ $this->get_bulk_page_slug() ][0][0] = __( 'Bulk Optimization', 'imagify' ); // WPCS: override ok.
+ }
+
+ if ( $screen_id ) {
+ // On the "Other Media optimized by Imagify" page, load the data.
+ add_action( 'load-' . $screen_id, array( $this, 'load_files_list' ) );
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PLUGIN ACTION LINKS ===================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Add links to the plugin row in the plugins list.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param array $actions An array of action links.
+ * @return array
+ */
+ public function plugin_action_links( $actions ) {
+ array_unshift( $actions, sprintf( '%s ', esc_url( imagify_get_external_url( 'documentation' ) ), __( 'Documentation', 'imagify' ) ) );
+ array_unshift( $actions, sprintf( '%s ', esc_url( get_imagify_admin_url( 'bulk-optimization' ) ), __( 'Bulk Optimization', 'imagify' ) ) );
+ array_unshift( $actions, sprintf( '%s ', esc_url( get_imagify_admin_url() ), __( 'Settings', 'imagify' ) ) );
+ return $actions;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** MAIN PAGE TEMPLATES ===================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * The main settings page.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function display_settings_page() {
+ $this->print_template( 'page-settings' );
+ }
+
+ /**
+ * The bulk optimization page.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function display_bulk_page() {
+ $types = array();
+ $data = array(
+ // Limits.
+ 'unoptimized_attachment_limit' => 0,
+ // What to optimize.
+ 'icon' => 'images-alt2',
+ 'title' => __( 'Optimize your media files', 'imagify' ),
+ 'groups' => array(),
+ );
+
+ if ( imagify_is_screen( 'bulk' ) ) {
+ if ( ! is_network_admin() ) {
+ /**
+ * Library: in each site.
+ */
+ $types['library|wp'] = 1;
+ }
+
+ if ( imagify_can_optimize_custom_folders() && ( imagify_is_active_for_network() && is_network_admin() || ! imagify_is_active_for_network() ) ) {
+ /**
+ * Custom folders: in network admin only if network activated, in each site otherwise.
+ */
+ $types['custom-folders|custom-folders'] = 1;
+ }
+ }
+
+ /**
+ * Filter the types to display in the bulk optimization page.
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ *
+ * @param array $types The folder types displayed on the page. If a folder type is "library", the context should be suffixed after a pipe character. They are passed as array keys.
+ */
+ $types = apply_filters( 'imagify_bulk_page_types', $types );
+ $types = array_filter( (array) $types );
+
+ if ( isset( $types['library|wp'] ) ) {
+ // Limits.
+ $data['unoptimized_attachment_limit'] += imagify_get_unoptimized_attachment_limit();
+ // Group.
+ $data['groups']['library'] = array(
+ /**
+ * The group_id corresponds to the file names like 'part-bulk-optimization-results-row-{$group_id}'.
+ * It is also used in get_imagify_localize_script_translations().
+ */
+ 'group_id' => 'library',
+ 'context' => 'wp',
+ 'title' => __( 'Media Library', 'imagify' ),
+ /* translators: 1 is the opening of a link, 2 is the closing of this link. */
+ 'footer' => sprintf( __( 'You can also re-optimize your media files from your %1$sMedia Library%2$s screen.', 'imagify' ), '', ' ' ),
+ );
+ }
+
+ if ( isset( $types['custom-folders|custom-folders'] ) ) {
+ if ( ! Imagify_Folders_DB::get_instance()->has_items() ) {
+ // New Feature!
+ $data['no-custom-folders'] = true;
+ } elseif ( Imagify_Folders_DB::get_instance()->has_active_folders() ) {
+ // Group.
+ $data['groups']['custom-folders'] = array(
+ 'group_id' => 'custom-folders',
+ 'context' => 'custom-folders',
+ 'title' => __( 'Custom folders', 'imagify' ),
+ /* translators: 1 is the opening of a link, 2 is the closing of this link. */
+ 'footer' => sprintf( __( 'You can re-optimize your media files more finely directly in the %1$smedia management%2$s.', 'imagify' ), '', ' ' ),
+ );
+ }
+ }
+
+ // Add generic stats.
+ $data = array_merge( $data, imagify_get_bulk_stats( $types, array(
+ 'fullset' => true,
+ ) ) );
+
+ /**
+ * Filter the data to use on the bulk optimization page.
+ *
+ * @since 1.7
+ * @since 1.7.1 Added the $types parameter.
+ * @author Grégory Viguier
+ *
+ * @param array $data The data to use.
+ * @param array $types The folder types displayed on the page. They are passed as array keys.
+ */
+ $data = apply_filters( 'imagify_bulk_page_data', $data, $types );
+
+ $this->print_template( 'page-bulk', $data );
+ }
+
+ /**
+ * The page displaying the "custom folders" files.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function display_files_list() {
+ $this->print_template( 'page-files-list' );
+ }
+
+ /**
+ * Initiate the "custom folders" list table data.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function load_files_list() {
+ // Instantiate the list.
+ $this->list_table = new Imagify_Files_List_Table( array(
+ 'screen' => 'imagify-files',
+ ) );
+
+ // Query the Items.
+ $this->list_table->prepare_items();
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** GETTERS ================================================================================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the settings page slug.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return string
+ */
+ public function get_settings_page_slug() {
+ return $this->slug_settings;
+ }
+
+ /**
+ * Get the bulk optimization page slug.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return string
+ */
+ public function get_bulk_page_slug() {
+ return $this->slug_bulk;
+ }
+
+ /**
+ * Get the "custom folders" files page slug.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return string
+ */
+ public function get_files_page_slug() {
+ return $this->slug_files;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PAGE TESTS ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if weâre displaying the settings page.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_settings_page() {
+ global $pagenow;
+
+ $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
+
+ if ( $this->get_settings_page_slug() !== $page ) {
+ return false;
+ }
+
+ if ( imagify_is_active_for_network() ) {
+ return 'admin.php' === $pagenow;
+ }
+
+ return 'options-general.php' === $pagenow;
+ }
+
+ /**
+ * Tell if weâre displaying the bulk optimization page.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_bulk_page() {
+ global $pagenow;
+
+ $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
+
+ return 'upload.php' === $pagenow && $this->get_bulk_page_slug() === $page;
+ }
+
+ /**
+ * Tell if weâre displaying the custom files list page.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_files_page() {
+ global $pagenow;
+
+ $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
+
+ return 'upload.php' === $pagenow && $this->get_files_page_slug() === $page;
+ }
+
+ /**
+ * Tell if weâre displaying the WP media library page.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_wp_library_page() {
+ global $pagenow;
+
+ $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
+
+ return 'upload.php' === $pagenow && ! $page;
+ }
+
+ /**
+ * Tell if weâre displaying a media page.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_media_page() {
+ global $pagenow, $typenow;
+
+ return 'post.php' === $pagenow && 'attachment' === $typenow;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** QUOTA =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the remaining quota in percent.
+ *
+ * @since 1.8.1
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return int
+ */
+ public function get_quota_percent() {
+ static $quota;
+
+ if ( isset( $quota ) ) {
+ return $quota;
+ }
+
+ $user = new Imagify_User();
+ $quota = $user->get_percent_unconsumed_quota();
+
+ return $quota;
+ }
+
+ /**
+ * Get the HTML class used for the quota (to change the color when out of quota for example).
+ *
+ * @since 1.8.1
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return string
+ */
+ public function get_quota_class() {
+ static $class;
+
+ if ( isset( $class ) ) {
+ return $class;
+ }
+
+ $quota = $this->get_quota_percent();
+ $class = 'imagify-bar-';
+
+ if ( $quota <= 20 ) {
+ $class .= 'negative';
+ } elseif ( $quota <= 50 ) {
+ $class .= 'neutral';
+ } else {
+ $class .= 'positive';
+ }
+
+ return $class;
+ }
+
+ /**
+ * Get the HTML tag used for the quota (the weather-like icon).
+ *
+ * @since 1.8.1
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return string
+ */
+ public function get_quota_icon() {
+ static $icon;
+
+ if ( isset( $icon ) ) {
+ return $icon;
+ }
+
+ $quota = $this->get_quota_percent();
+
+ if ( $quota <= 20 ) {
+ $icon = ' ';
+ } elseif ( $quota <= 50 ) {
+ $icon = ' ';
+ } else {
+ $icon = ' ';
+ }
+
+ return $icon;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** GENERIC TEMPLATE TOOLS ================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get a template contents.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $template The template name.
+ * @param mixed $data Some data to pass to the template.
+ * @return string|bool The page contents. False if the template doesn't exist.
+ */
+ public function get_template( $template, $data = array() ) {
+ $path = str_replace( '_', '-', $template );
+ $path = IMAGIFY_PATH . 'views/' . $template . '.php';
+
+ if ( ! $this->filesystem->exists( $path ) ) {
+ return false;
+ }
+
+ ob_start();
+ include $path;
+ $contents = ob_get_clean();
+
+ return trim( (string) $contents );
+ }
+
+ /**
+ * Print a template.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $template The template name.
+ * @param mixed $data Some data to pass to the template.
+ */
+ public function print_template( $template, $data = array() ) {
+ echo $this->get_template( $template, $data );
+ }
+
+ /**
+ * Add a template to the list of JS templates to print at the end of the page.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param string $template The template name.
+ */
+ public function print_js_template_in_footer( $template ) {
+ if ( isset( $this->templates_in_footer[ $template ] ) ) {
+ return;
+ }
+
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+ return;
+ }
+
+ switch ( $template ) {
+ case 'button/processing':
+ $data = [ 'label' => '{{ data.label }}' ];
+ break;
+ default:
+ $data = [];
+ }
+
+ $this->templates_in_footer[ $template ] = $data;
+ }
+
+ /**
+ * Print the JS templates that have been added to the "queue".
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function print_js_templates() {
+ if ( ! $this->templates_in_footer ) {
+ return;
+ }
+
+ foreach ( $this->templates_in_footer as $template => $data ) {
+ $template_id = str_replace( [ '/', '_' ], '-', $template );
+
+ echo '';
+ }
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** TOOLS =================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Create HTML attributes from an array.
+ *
+ * @since 1.9
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $attributes A list of attribute pairs.
+ * @return string HTML attributes.
+ */
+ public function build_attributes( $attributes ) {
+ if ( ! $attributes || ! is_array( $attributes ) ) {
+ return '';
+ }
+
+ $out = '';
+
+ foreach ( $attributes as $attribute => $value ) {
+ if ( '' === $value ) {
+ continue;
+ }
+
+ $out .= ' ' . $attribute . '="' . esc_attr( $value ) . '"';
+ }
+
+ return $out;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/classes/class-imagify.php b/wp-content/plugins/imagify/inc/classes/class-imagify.php
new file mode 100644
index 00000000..8a688d49
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/classes/class-imagify.php
@@ -0,0 +1,677 @@
+api_key = get_imagify_option( 'api_key' );
+ $this->secure_key = $this->generate_secure_key();
+ $this->filesystem = Imagify_Filesystem::get_instance();
+
+ $this->all_headers['Accept'] = 'Accept: application/json';
+ $this->all_headers['Content-Type'] = 'Content-Type: application/json';
+ $this->all_headers['Authorization'] = 'Authorization: token ' . $this->api_key;
+ }
+
+ /**
+ * Get your Imagify account infos.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @return object
+ */
+ public function get_user() {
+ global $wp_current_filter;
+
+ if ( isset( static::$user ) ) {
+ return static::$user;
+ }
+
+ if ( in_array( 'upgrader_post_install', (array) $wp_current_filter, true ) ) {
+ // Dirty patch used when updating from 1.7.
+ static::$user = new WP_Error();
+ return static::$user;
+ }
+
+ $this->headers = $this->all_headers;
+ static::$user = $this->http_call( 'users/me/', [ 'timeout' => 10 ] );
+
+ if ( is_wp_error( static::$user ) ) {
+ return static::$user;
+ }
+
+ $maybe_missing = [
+ 'account_type' => 'free',
+ 'quota' => 0,
+ 'extra_quota' => 0,
+ 'extra_quota_consumed' => 0,
+ 'consumed_current_month_quota' => 0,
+ ];
+
+ foreach ( $maybe_missing as $name => $value ) {
+ if ( ! isset( static::$user->$name ) ) {
+ static::$user->$name = $value;
+ }
+ }
+
+ return static::$user;
+ }
+
+ /**
+ * Create a user on your Imagify account.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @param array $data All user data.
+ * @return object
+ */
+ public function create_user( $data ) {
+ $this->headers = [];
+ $data = array_merge(
+ $data,
+ [
+ 'from_plugin' => true,
+ 'partner' => imagify_get_partner(),
+ ]
+ );
+
+ if ( ! $data['partner'] ) {
+ unset( $data['partner'] );
+ }
+
+ $response = $this->http_call(
+ 'users/',
+ [
+ 'method' => 'POST',
+ 'post_data' => $data,
+ ]
+ );
+
+ if ( ! is_wp_error( $response ) ) {
+ imagify_delete_partner();
+ }
+
+ return $response;
+ }
+
+ /**
+ * Update an existing user on your Imagify account.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @param string $data All user data.
+ * @return object
+ */
+ public function update_user( $data ) {
+ $this->headers = $this->all_headers;
+
+ return $this->http_call(
+ 'users/me/',
+ [
+ 'method' => 'PUT',
+ 'post_data' => $data,
+ 'timeout' => 10,
+ ]
+ );
+ }
+
+ /**
+ * Check your Imagify API key status.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @param string $data The license key.
+ * @return object
+ */
+ public function get_status( $data ) {
+ static $status = [];
+
+ if ( isset( $status[ $data ] ) ) {
+ return $status[ $data ];
+ }
+
+ $this->headers = [
+ 'Authorization' => 'Authorization: token ' . $data,
+ ];
+
+ $uri = 'status/';
+ $partner = imagify_get_partner();
+
+ if ( $partner ) {
+ $uri .= '?partner=' . $partner;
+ }
+
+ $status[ $data ] = $this->http_call( $uri, [ 'timeout' => 10 ] );
+
+ return $status[ $data ];
+ }
+
+ /**
+ * Get the Imagify API version.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @return object
+ */
+ public function get_api_version() {
+ static $api_version;
+
+ if ( ! isset( $api_version ) ) {
+ $this->headers = [
+ 'Authorization' => $this->all_headers['Authorization'],
+ ];
+
+ $api_version = $this->http_call( 'version/', [ 'timeout' => 5 ] );
+ }
+
+ return $api_version;
+ }
+
+ /**
+ * Get Public Info.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @return object
+ */
+ public function get_public_info() {
+ $this->headers = $this->all_headers;
+
+ return $this->http_call( 'public-info' );
+ }
+
+ /**
+ * Optimize an image from its binary content.
+ *
+ * @access public
+ * @since 1.6.5
+ * @since 1.6.7 $data['image'] can contain the file path (prefered) or the result of `curl_file_create()`.
+ *
+ * @param string $data All options.
+ * @return object
+ */
+ public function upload_image( $data ) {
+ $this->headers = [
+ 'Authorization' => $this->all_headers['Authorization'],
+ ];
+
+ return $this->http_call(
+ 'upload/',
+ [
+ 'method' => 'POST',
+ 'post_data' => $data,
+ ]
+ );
+ }
+
+ /**
+ * Optimize an image from its URL.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @param string $data All options. Details here: --.
+ * @return object
+ */
+ public function fetch_image( $data ) {
+ $this->headers = $this->all_headers;
+
+ return $this->http_call(
+ 'fetch/',
+ [
+ 'method' => 'POST',
+ 'post_data' => wp_json_encode( $data ),
+ ]
+ );
+ }
+
+ /**
+ * Get prices for plans.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @return object
+ */
+ public function get_plans_prices() {
+ $this->headers = $this->all_headers;
+
+ return $this->http_call( 'pricing/plan/' );
+ }
+
+ /**
+ * Get prices for packs (One Time).
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @return object
+ */
+ public function get_packs_prices() {
+ $this->headers = $this->all_headers;
+
+ return $this->http_call( 'pricing/pack/' );
+ }
+
+ /**
+ * Get all prices (packs & plans included).
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @return object
+ */
+ public function get_all_prices() {
+ $this->headers = $this->all_headers;
+
+ return $this->http_call( 'pricing/all/' );
+ }
+
+ /**
+ * Get all prices (packs & plans included).
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @param string $coupon A coupon code.
+ * @return object
+ */
+ public function check_coupon_code( $coupon ) {
+ $this->headers = $this->all_headers;
+
+ return $this->http_call( 'coupons/' . $coupon . '/' );
+ }
+
+ /**
+ * Get information about current discount.
+ *
+ * @access public
+ * @since 1.6.5
+ *
+ * @return object
+ */
+ public function check_discount() {
+ $this->headers = $this->all_headers;
+
+ return $this->http_call( 'pricing/discount/' );
+ }
+
+ /**
+ * Make an HTTP call using curl.
+ *
+ * @access private
+ * @since 1.6.5
+ * @since 1.6.7 Use `wp_remote_request()` when possible (when we don't need to send an image).
+ *
+ * @param string $url The URL to call.
+ * @param array $args The request args.
+ * @return object
+ */
+ private function http_call( $url, $args = [] ) {
+ $args = array_merge(
+ [
+ 'method' => 'GET',
+ 'post_data' => null,
+ 'timeout' => 45,
+ ],
+ $args
+ );
+
+ $endpoint = trim( $url, '/' );
+ /**
+ * Filter the timeout value for any request to the API.
+ *
+ * @since 1.6.7
+ * @author Grégory Viguier
+ *
+ * @param int $timeout Timeout value in seconds.
+ * @param string $endpoint The targetted endpoint. It's basically URI without heading nor trailing slash.
+ */
+ $args['timeout'] = apply_filters( 'imagify_api_http_request_timeout', $args['timeout'], $endpoint );
+
+ // We need to send an image: we must use cURL directly.
+ if ( isset( $args['post_data']['image'] ) ) {
+ return $this->curl_http_call( $url, $args );
+ }
+
+ $args = array_merge(
+ [
+ 'headers' => [],
+ 'body' => $args['post_data'],
+ 'sslverify' => apply_filters( 'https_ssl_verify', false ),
+ ],
+ $args
+ );
+
+ unset( $args['post_data'] );
+
+ if ( $this->headers ) {
+ foreach ( $this->headers as $name => $value ) {
+ $value = explode( ':', $value, 2 );
+ $value = end( $value );
+
+ $args['headers'][ $name ] = trim( $value );
+ }
+ }
+
+ if ( ! empty( $args['headers']['Authorization'] ) ) {
+ // Make sure our API has not overwritten by some other plugin.
+ $args[ $this->secure_key ] = preg_replace( '/^token /', '', $args['headers']['Authorization'] );
+
+ if ( ! has_filter( 'http_request_args', [ $this, 'force_api_key_header' ] ) ) {
+ add_filter( 'http_request_args', [ $this, 'force_api_key_header' ], IMAGIFY_INT_MAX + 25, 2 );
+ }
+ }
+
+ $response = wp_remote_request( self::API_ENDPOINT . $url, $args );
+
+ if ( is_wp_error( $response ) ) {
+ return $response;
+ }
+
+ $http_code = wp_remote_retrieve_response_code( $response );
+ $response = wp_remote_retrieve_body( $response );
+
+ return $this->handle_response( $response, $http_code );
+ }
+
+ /**
+ * Make an HTTP call using curl.
+ *
+ * @access private
+ * @since 1.6.7
+ * @throws Exception When curl_init() fails.
+ * @author Grégory Viguier
+ *
+ * @param string $url The URL to call.
+ * @param array $args The request arguments.
+ * @return object
+ */
+ private function curl_http_call( $url, $args = [] ) {
+ // Check if curl is available.
+ if ( ! Imagify_Requirements::supports_curl() ) {
+ return new WP_Error( 'curl', 'cURL isn\'t installed on the server.' );
+ }
+
+ try {
+ $url = self::API_ENDPOINT . $url;
+ $ch = curl_init();
+
+ if ( false === $ch ) {
+ throw new Exception( 'Could not initialize a new cURL handle' );
+ }
+
+ if ( isset( $args['post_data']['image'] ) && is_string( $args['post_data']['image'] ) && $this->filesystem->exists( $args['post_data']['image'] ) ) {
+ $args['post_data']['image'] = curl_file_create( $args['post_data']['image'] );
+ }
+
+ // Handle proxies.
+ $proxy = new WP_HTTP_Proxy();
+
+ if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
+ curl_setopt( $ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
+ curl_setopt( $ch, CURLOPT_PROXY, $proxy->host() );
+ curl_setopt( $ch, CURLOPT_PROXYPORT, $proxy->port() );
+
+ if ( $proxy->use_authentication() ) {
+ curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
+ curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
+ }
+ }
+
+ if ( 'POST' === $args['method'] ) {
+ curl_setopt( $ch, CURLOPT_POST, true );
+ curl_setopt( $ch, CURLOPT_POSTFIELDS, $args['post_data'] );
+ } elseif ( 'PUT' === $args['method'] ) {
+ curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'PUT' );
+ curl_setopt( $ch, CURLOPT_POSTFIELDS, $args['post_data'] );
+ }
+
+ if ( defined( 'CURLOPT_PROTOCOLS' ) ) {
+ curl_setopt( $ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
+ }
+
+ $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) );
+
+ curl_setopt( $ch, CURLOPT_URL, $url );
+ curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
+ curl_setopt( $ch, CURLOPT_HEADER, false );
+ curl_setopt( $ch, CURLOPT_HTTPHEADER, $this->headers );
+ curl_setopt( $ch, CURLOPT_TIMEOUT, $args['timeout'] );
+ curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $args['timeout'] );
+ curl_setopt( $ch, CURLOPT_USERAGENT, $user_agent );
+ @curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, false );
+ curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false );
+ curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
+
+ /**
+ * Tell which http version to use with cURL during image optimization.
+ *
+ * @since 1.8.4.1
+ * @since 1.9.9 Default value is `false`.
+ * @author Grégory Viguier
+ *
+ * @param $use_version_1_0 bool True to use version 1.0. False for 1.1. Default is false.
+ */
+ if ( apply_filters( 'imagify_curl_http_version_1_0', false ) ) {
+ curl_setopt( $ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
+ } else {
+ curl_setopt( $ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
+ }
+
+ $response = curl_exec( $ch );
+ $error = curl_error( $ch );
+ $http_code = (int) curl_getinfo( $ch, CURLINFO_HTTP_CODE );
+
+ if ( is_resource( $ch ) ) {
+ curl_close( $ch );
+ } else {
+ unset( $ch );
+ }
+ } catch ( Exception $e ) {
+ $args['headers'] = $this->headers;
+ /**
+ * Fires after a failed curl request.
+ *
+ * @since 1.6.9
+ * @author Grégory Viguier
+ *
+ * @param string $url The requested URL.
+ * @param array $args The request arguments.
+ * @param object $e The raised Exception.
+ */
+ do_action( 'imagify_curl_http_response', $url, $args, $e );
+
+ return new WP_Error( 'curl', 'An error occurred (' . $e->getMessage() . ')' );
+ } // End try().
+
+ $args['headers'] = $this->headers;
+
+ /**
+ * Fires after a successful curl request.
+ *
+ * @since 1.6.9
+ * @author Grégory Viguier
+ *
+ * @param string $url The requested URL.
+ * @param array $args The request arguments.
+ * @param string $response The request response.
+ * @param int $http_code The request HTTP code.
+ * @param string $error An error message.
+ */
+ do_action( 'imagify_curl_http_response', $url, $args, $response, $http_code, $error );
+
+ return $this->handle_response( $response, $http_code, $error );
+ }
+
+ /**
+ * Handle the request response and maybe trigger an error.
+ *
+ * @access private
+ * @since 1.6.7
+ * @author Grégory Viguier
+ *
+ * @param string $response The request response.
+ * @param int $http_code The request HTTP code.
+ * @param string $error An error message.
+ * @return object
+ */
+ private function handle_response( $response, $http_code, $error = '' ) {
+ $response = json_decode( $response );
+
+ if ( 401 === $http_code ) {
+ // Reset the API validity cache if the API key is not valid.
+ Imagify_Requirements::reset_cache( 'api_key_valid' );
+ }
+
+ if ( 200 !== $http_code && ! empty( $response->code ) ) {
+ if ( ! empty( $response->detail ) ) {
+ return new WP_Error( 'error ' . $http_code, $response->detail );
+ }
+ if ( ! empty( $response->image ) ) {
+ $error = (array) $response->image;
+ $error = reset( $error );
+ return new WP_Error( 'error ' . $http_code, $error );
+ }
+ }
+
+ if ( 413 === $http_code ) {
+ return new WP_Error( 'error ' . $http_code, 'Your image is too big to be uploaded on our server.' );
+ }
+
+ if ( 200 !== $http_code ) {
+ $error = trim( (string) $error );
+ $error = '' !== $error ? ' - ' . htmlentities( $error ) : '';
+ return new WP_Error( 'error ' . $http_code, "Our server returned an error ({$http_code}{$error})" );
+ }
+
+ if ( ! is_object( $response ) ) {
+ return new WP_Error( 'invalid response', 'Our server returned an invalid response.', $response );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Generate a random key.
+ * Similar to wp_generate_password() but without filter.
+ *
+ * @access private
+ * @since 1.8.4
+ * @see wp_generate_password()
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ private function generate_secure_key() {
+ $length = wp_rand( 12, 20 );
+ $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|';
+ $password = '';
+
+ for ( $i = 0; $i < $length; $i++ ) {
+ $password .= substr( $chars, wp_rand( 0, strlen( $chars ) - 1 ), 1 );
+ }
+
+ return $password;
+ }
+
+ /**
+ * Filter the arguments used in an HTTP request, to make sure our API key has not been overwritten by some other plugin.
+ *
+ * @access public
+ * @since 1.8.4
+ * @author Grégory Viguier
+ *
+ * @param array $args An array of HTTP request arguments.
+ * @param string $url The request URL.
+ * @return array
+ */
+ public function force_api_key_header( $args, $url ) {
+ if ( strpos( $url, self::API_ENDPOINT ) === false ) {
+ return $args;
+ }
+
+ if ( ! empty( $args['headers']['Authorization'] ) || ! empty( $args[ $this->secure_key ] ) ) {
+ if ( ! empty( $args[ $this->secure_key ] ) ) {
+ $args['headers']['Authorization'] = 'token ' . $args[ $this->secure_key ];
+ } else {
+ $args['headers']['Authorization'] = 'token ' . $this->api_key;
+ }
+ }
+
+ return $args;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/common/admin-bar.php b/wp-content/plugins/imagify/inc/common/admin-bar.php
new file mode 100644
index 00000000..af89ec43
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/common/admin-bar.php
@@ -0,0 +1,79 @@
+current_user_can( 'manage' ) ) {
+ return;
+ }
+
+ if ( ! get_imagify_option( 'admin_bar_menu' ) ) {
+ return;
+ }
+
+ // Parent.
+ $wp_admin_bar->add_menu( array(
+ 'id' => 'imagify',
+ 'title' => 'Imagify',
+ 'href' => get_imagify_admin_url(),
+ ) );
+
+ // Settings.
+ $wp_admin_bar->add_menu(array(
+ 'parent' => 'imagify',
+ 'id' => 'imagify-settings',
+ 'title' => __( 'Settings' ),
+ 'href' => get_imagify_admin_url(),
+ ) );
+
+ // Bulk Optimization.
+ if ( ! is_network_admin() ) {
+ $wp_admin_bar->add_menu(array(
+ 'parent' => 'imagify',
+ 'id' => 'imagify-bulk-optimization',
+ 'title' => __( 'Bulk Optimization', 'imagify' ),
+ 'href' => get_imagify_admin_url( 'bulk-optimization' ),
+ ) );
+ }
+
+ // Documentation.
+ $wp_admin_bar->add_menu(array(
+ 'parent' => 'imagify',
+ 'id' => 'imagify-documentation',
+ 'title' => __( 'Documentation', 'imagify' ),
+ 'href' => imagify_get_external_url( 'documentation' ),
+ 'meta' => array(
+ 'target' => '_blank',
+ ),
+ ) );
+
+ // Rate it.
+ $wp_admin_bar->add_menu(array(
+ 'parent' => 'imagify',
+ 'id' => 'imagify-rate-it',
+ /* translators: %s is WordPress.org. */
+ 'title' => sprintf( __( 'Rate Imagify on %s', 'imagify' ), 'WordPress.org' ),
+ 'href' => imagify_get_external_url( 'rate' ),
+ 'meta' => array(
+ 'target' => '_blank',
+ ),
+ ) );
+
+ // Quota & Profile informations.
+ if ( defined( 'IMAGIFY_HIDDEN_ACCOUNT' ) && IMAGIFY_HIDDEN_ACCOUNT || ! get_imagify_option( 'api_key' ) ) {
+ return;
+ }
+
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'imagify',
+ 'id' => 'imagify-profile',
+ 'title' => wp_nonce_field( 'imagify-get-admin-bar-profile', 'imagifygetadminbarprofilenonce', false, false ) . '' . __( 'Loading...', 'imagify' ) . '
',
+ ) );
+}
diff --git a/wp-content/plugins/imagify/inc/common/attachments.php b/wp-content/plugins/imagify/inc/common/attachments.php
new file mode 100644
index 00000000..5b713816
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/common/attachments.php
@@ -0,0 +1,69 @@
+is_valid() ) {
+ return;
+ }
+
+ imagify_trigger_delete_media_hook( $process );
+}
+
+add_action( 'imagify_delete_media', 'imagify_cleanup_after_media_deletion' );
+/**
+ * Delete the backup file and the webp files when an attachement is deleted.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process An optimization process.
+ */
+function imagify_cleanup_after_media_deletion( $process ) {
+ if ( 'wp' !== $process->get_media()->get_context() ) {
+ return;
+ }
+
+ /**
+ * The optimization data will be automatically deleted by WP (post metas).
+ * Delete the webp versions and the backup file.
+ */
+ $process->delete_webp_files();
+ $process->delete_backup();
+}
+
+add_filter( 'ext2type', 'imagify_add_webp_type' );
+/**
+ * Add the webp extension to wp_get_ext_types().
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param array $ext2type Multi-dimensional array with extensions for a default set of file types.
+ * @return array
+ */
+function imagify_add_webp_type( $ext2type ) {
+ if ( ! in_array( 'webp', $ext2type['image'], true ) ) {
+ $ext2type['image'][] = 'webp';
+ }
+ return $ext2type;
+}
+
+/**
+ * Set WPâs "big images threshold" to Imagifyâs resizing value.
+ *
+ * @since 1.9.8
+ * @since WP 5.3
+ * @author Grégory Viguier
+ */
+add_filter( 'big_image_size_threshold', [ imagify_get_context( 'wp' ), 'get_resizing_threshold' ], IMAGIFY_INT_MAX );
diff --git a/wp-content/plugins/imagify/inc/common/partners.php b/wp-content/plugins/imagify/inc/common/partners.php
new file mode 100644
index 00000000..6040b1cd
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/common/partners.php
@@ -0,0 +1,33 @@
+get_option_name(), 'imagify_maybe_delete_partner_on_option_update', 10, 2 );
+/**
+ * After the first API key has been successfully added, make sure the partner ID is deleted.
+ *
+ * @since 1.6.14
+ * @author Grégory Viguier
+ *
+ * @param mixed $old_value The old option value.
+ * @param mixed $new_value The new option value.
+ */
+function imagify_maybe_delete_partner_on_option_update( $old_value, $new_value ) {
+ if ( empty( $old_value['api_key'] ) && ! empty( $new_value['api_key'] ) ) {
+ imagify_delete_partner();
+ }
+}
+
+add_action( 'update_site_option_' . Imagify_Options::get_instance()->get_option_name(), 'imagify_maybe_delete_partner_on_network_option_update', 10, 3 );
+/**
+ * After the first API key has been successfully added to the network option, make sure the partner ID is deleted.
+ *
+ * @since 1.6.14
+ * @author Grégory Viguier
+ *
+ * @param string $option Name of the network option.
+ * @param mixed $new_value The new network option value.
+ * @param mixed $old_value The old network option value.
+ */
+function imagify_maybe_delete_partner_on_network_option_update( $option, $new_value, $old_value ) {
+ imagify_maybe_delete_partner_on_option_update( $old_value, $new_value );
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/3rd-party.php b/wp-content/plugins/imagify/inc/deprecated/3rd-party.php
new file mode 100644
index 00000000..ad600a5d
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/3rd-party.php
@@ -0,0 +1,331 @@
+maybe_upgrade_table()' );
+
+ \Imagify\ThirdParty\NGG\DB::get_instance()->maybe_upgrade_table();
+ }
+
+ /**
+ * Update all Imagify stats for NGG Bulk Optimization.
+ *
+ * @since 1.5
+ * @since 1.7 Deprecated.
+ * @author Jonathan Buttigieg
+ * @deprecated
+ */
+ function _imagify_ngg_update_bulk_stats() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'imagify_ngg_bulk_page_data()' );
+
+ if ( empty( $_GET['page'] ) || imagify_get_ngg_bulk_screen_slug() !== $_GET['page'] ) { // WPCS: CSRF ok.
+ return;
+ }
+
+ add_filter( 'imagify_count_attachments' , 'imagify_ngg_count_attachments' );
+ add_filter( 'imagify_count_optimized_attachments' , 'imagify_ngg_count_optimized_attachments' );
+ add_filter( 'imagify_count_error_attachments' , 'imagify_ngg_count_error_attachments' );
+ add_filter( 'imagify_count_unoptimized_attachments' , 'imagify_ngg_count_unoptimized_attachments' );
+ add_filter( 'imagify_percent_optimized_attachments' , 'imagify_ngg_percent_optimized_attachments' );
+ add_filter( 'imagify_count_saving_data' , 'imagify_ngg_count_saving_data', 8 );
+ }
+
+ /**
+ * Prepare the data that goes back with the Heartbeat API.
+ *
+ * @since 1.5
+ * @since 1.7.1 Deprecated.
+ * @deprecated
+ *
+ * @param array $response The Heartbeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ function _imagify_ngg_heartbeat_received( $response, $data ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1' );
+
+ if ( ! isset( $data['imagify_heartbeat'] ) || 'update_ngg_bulk_data' !== $data['imagify_heartbeat'] ) {
+ return $response;
+ }
+
+ add_filter( 'imagify_count_saving_data', 'imagify_ngg_count_saving_data', 8 );
+ $saving_data = imagify_count_saving_data();
+ $user = new Imagify_User();
+
+ $response['imagify_bulk_data'] = array(
+ // User account.
+ 'unconsumed_quota' => is_wp_error( $user ) ? 0 : $user->get_percent_unconsumed_quota(),
+ // Global chart.
+ 'optimized_attachments_percent' => imagify_ngg_percent_optimized_attachments(),
+ 'unoptimized_attachments' => imagify_ngg_count_unoptimized_attachments(),
+ 'optimized_attachments' => imagify_ngg_count_optimized_attachments(),
+ 'errors_attachments' => imagify_ngg_count_error_attachments(),
+ // Stats block.
+ 'already_optimized_attachments' => number_format_i18n( $saving_data['count'] ),
+ 'original_human' => imagify_size_format( $saving_data['original_size'], 1 ),
+ 'optimized_human' => imagify_size_format( $saving_data['optimized_size'], 1 ),
+ 'optimized_percent' => $saving_data['percent'],
+ );
+
+ return $response;
+ }
+
+ /**
+ * Filter the current user capability to operate Imagify.
+ *
+ * @since 1.6.11
+ * @since 1.9 Deprecated.
+ * @see imagify_get_capacity()
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param bool $user_can Tell if the current user has the required capacity to operate Imagify.
+ * @param string $capacity The user capacity.
+ * @param string $describer Capacity describer. See imagify_get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $post_id A post ID (a gallery ID for NGG).
+ * @return bool
+ */
+ function imagify_ngg_current_user_can( $user_can, $capacity, $describer, $post_id ) {
+ static $user_can_per_gallery = array();
+
+ _deprecated_function( __FUNCTION__ . '()', '1.9' );
+
+ if ( ! $user_can || ! $post_id || 'NextGEN Manage gallery' !== $capacity ) {
+ return $user_can;
+ }
+
+ $image = nggdb::find_image( $post_id );
+
+ if ( isset( $user_can_per_gallery[ $image->galleryid ] ) ) {
+ return $user_can_per_gallery[ $image->galleryid ];
+ }
+
+ $gallery_mapper = C_Gallery_Mapper::get_instance();
+ $gallery = $gallery_mapper->find( $image->galleryid, false );
+
+ if ( get_current_user_id() === $gallery->author || current_user_can( 'NextGEN Manage others gallery' ) ) {
+ // The user created this gallery or can edit others galleries.
+ $user_can_per_gallery[ $image->galleryid ] = true;
+ return $user_can_per_gallery[ $image->galleryid ];
+ }
+
+ // The user can't edit this gallery.
+ $user_can_per_gallery[ $image->galleryid ] = false;
+ return $user_can_per_gallery[ $image->galleryid ];
+ }
+
+ /**
+ * Get user capacity to operate Imagify within NGG galleries.
+ * It is meant to be used to filter 'imagify_capacity'.
+ *
+ * @since 1.6.11
+ * @since 1.9 Deprecated.
+ * @see imagify_get_capacity()
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $capacity The user capacity.
+ * @param string $describer Capacity describer. See imagify_get_capacity() for possible values. Can also be a "real" user capacity.
+ * @return string
+ */
+ function imagify_get_ngg_capacity( $capacity = 'edit_post', $describer = 'manual-optimize' ) {
+ if ( 'manual-optimize' === $describer ) {
+ return 'NextGEN Manage gallery';
+ }
+
+ return $capacity;
+ }
+
+ /**
+ * Dispatch the optimization process.
+ *
+ * @since 1.8
+ * @since 1.9 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ */
+ function imagify_ngg_dispatch_dynamic_thumbnail_background_process() {
+ _deprecated_function( __FUNCTION__ . '()', '1.9' );
+
+ Imagify_NGG_Dynamic_Thumbnails_Background_Process::get_instance()->save()->dispatch();
+ }
+
+ /**
+ * On manual optimization, manual re-optimization, and manual restoration, filter the user capacity to operate Imagify within NGG.
+ *
+ * @since 1.6.11
+ * @since 1.9 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ */
+ function _do_admin_post_imagify_ngg_user_capacity() {
+ _deprecated_function( __FUNCTION__ . '()', '1.9' );
+
+ if ( ! empty( $_GET['context'] ) && 'NGG' === $_GET['context'] ) { // WPCS: CSRF ok.
+ add_filter( 'imagify_capacity', 'imagify_get_ngg_capacity', 10, 2 );
+ }
+ }
+
+ /**
+ * Get all unoptimized attachment ids.
+ *
+ * @since 1.0
+ * @since 1.9 Deprecated
+ * @author Jonathan Buttigieg
+ * @deprecated
+ */
+ function _do_wp_ajax_imagify_ngg_get_unoptimized_attachment_ids() {
+ _deprecated_function( __FUNCTION__ . '()', '1.9', '\\Imagify\\ThirdParty\\NGG\\AdminAjaxPost::get_instance()->get_media_ids()' );
+
+ \Imagify\ThirdParty\NGG\AdminAjaxPost::get_instance()->get_media_ids();
+ }
+
+ /**
+ * Provide custom folder type data.
+ *
+ * @since 1.7
+ * @since 1.9 Deprecated
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param array $data An array with keys corresponding to cell classes, and values formatted with HTML.
+ * @param string $context A context.
+ * @return array
+ */
+ function imagify_ngg_get_folder_type_data( $data, $context ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9' );
+
+ if ( 'ngg' !== $context ) {
+ return $data;
+ }
+
+ // Already filtered in imagify_ngg_bulk_page_data().
+ $total_saving_data = imagify_count_saving_data();
+
+ return [
+ 'images-optimized' => imagify_ngg_count_optimized_attachments(),
+ 'errors' => imagify_ngg_count_error_attachments(),
+ 'optimized' => $total_saving_data['optimized_size'],
+ 'original' => $total_saving_data['original_size'],
+ 'errors_url' => admin_url( 'admin.php?page=nggallery-manage-gallery' ),
+ ];
+ }
+
+endif;
+
+if ( function_exists( 'wr2x_delete_attachment' ) ) :
+
+ /**
+ * Remove all retina versions if they exist.
+ *
+ * @since 1.0
+ * @since 1.8 Deprecated.
+ * @deprecated
+ *
+ * @param int $attachment_id An attachment ID.
+ */
+ function _imagify_wr2x_delete_attachment_on_restore( $attachment_id ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.8' );
+
+ wr2x_delete_attachment( $attachment_id );
+ }
+
+ /**
+ * Regenerate all retina versions.
+ *
+ * @since 1.0
+ * @since 1.8 Deprecated.
+ * @deprecated
+ *
+ * @param int $attachment_id An attachment ID.
+ */
+ function _imagify_wr2x_generate_images_on_restore( $attachment_id ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.8' );
+
+ wr2x_delete_attachment( $attachment_id );
+ wr2x_generate_images( wp_get_attachment_metadata( $attachment_id ) );
+ }
+
+ /**
+ * Filter the optimization data of each thumbnail.
+ *
+ * @since 1.0
+ * @since 1.8 Deprecated.
+ * @deprecated
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param int $id The attachment ID.
+ * @param string $path The attachment path.
+ * @param string $url The attachment URL.
+ * @param string $size_key The attachment size key.
+ * @param bool $optimization_level The optimization level.
+ * @return array $data The new optimization data.
+ */
+ function _imagify_optimize_wr2x( $data, $response, $id, $path, $url, $size_key, $optimization_level ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.8', 'Imagify_WP_Retina_2x::optimize_retina_version()' );
+
+ /**
+ * Allow to optimize the retina version generated by WP Retina x2.
+ *
+ * @since 1.0
+ *
+ * @param bool $do_retina True will force the optimization.
+ */
+ $do_retina = apply_filters( 'do_imagify_optimize_retina', true );
+ $retina_path = wr2x_get_retina( $path );
+
+ if ( empty( $retina_path ) || ! $do_retina ) {
+ return $data;
+ }
+
+ $response = do_imagify( $retina_path, array(
+ 'backup' => false,
+ 'optimization_level' => $optimization_level,
+ 'context' => 'wp-retina',
+ ) );
+ $attachment = get_imagify_attachment( 'wp', $id, 'imagify_fill_thumbnail_data' );
+
+ return $attachment->fill_data( $data, $response, $size_key . '@2x' );
+ }
+
+endif;
+
+if ( defined( 'WP_ROCKET_VERSION' ) ) :
+
+ /**
+ * Don't load Imagify CSS & JS files on WP Rocket options screen to avoid conflict with older version of SweetAlert.
+ * Since 1.6.10 they should be enqueued only if one of our notices displays here.
+ *
+ * @since 1.6.9.1
+ * @since 1.6.10 Use the new class Imagify_Assets.
+ * @since 1.9.3 Deprecated.
+ * @author Jonathan Buttigieg
+ * @author Grégory Viguier
+ * @deprecated
+ */
+ function imagify_dequeue_sweetalert_wprocket() {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3', '\\Imagify\\ThirdParty\\WPRocket\\Main::dequeue_sweetalert()' );
+
+ if ( ! defined( 'WP_ROCKET_PLUGIN_SLUG' ) ) {
+ return;
+ }
+
+ if ( ! imagify_is_screen( 'settings_page_' . WP_ROCKET_PLUGIN_SLUG ) && ! imagify_is_screen( 'settings_page_' . WP_ROCKET_PLUGIN_SLUG . '-network' ) ) {
+ return;
+ }
+
+ Imagify_Assets::get_instance()->dequeue_script( array( 'sweetalert-core', 'sweetalert', 'notices' ) );
+ }
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/deprecated/Traits/Media/CustomFoldersDeprecatedTrait.php b/wp-content/plugins/imagify/inc/deprecated/Traits/Media/CustomFoldersDeprecatedTrait.php
new file mode 100644
index 00000000..d4c8e4c2
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/Traits/Media/CustomFoldersDeprecatedTrait.php
@@ -0,0 +1,44 @@
+get_fullsize_url()' );
+
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_url();
+ }
+
+ $row = $this->get_row();
+
+ if ( ! $row || empty( $row['path'] ) ) {
+ return false;
+ }
+
+ return \Imagify_Files_Scan::remove_placeholder( $row['path'], 'url' );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/Traits/Media/NGGDeprecatedTrait.php b/wp-content/plugins/imagify/inc/deprecated/Traits/Media/NGGDeprecatedTrait.php
new file mode 100644
index 00000000..6eee3965
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/Traits/Media/NGGDeprecatedTrait.php
@@ -0,0 +1,38 @@
+get_fullsize_url()' );
+
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_url();
+ }
+
+ return ! empty( $this->image->imageURL ) ? $this->image->imageURL : false;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/Traits/Media/NoopDeprecatedTrait.php b/wp-content/plugins/imagify/inc/deprecated/Traits/Media/NoopDeprecatedTrait.php
new file mode 100644
index 00000000..2bef3bba
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/Traits/Media/NoopDeprecatedTrait.php
@@ -0,0 +1,30 @@
+get_fullsize_url()' );
+
+ return false;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/Traits/Media/WPDeprecatedTrait.php b/wp-content/plugins/imagify/inc/deprecated/Traits/Media/WPDeprecatedTrait.php
new file mode 100644
index 00000000..0c89bc5d
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/Traits/Media/WPDeprecatedTrait.php
@@ -0,0 +1,40 @@
+get_fullsize_url()' );
+
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ if ( $this->get_cdn() ) {
+ return $this->get_cdn()->get_file_url();
+ }
+
+ $url = wp_get_attachment_url( $this->id );
+
+ return $url ? $url : false;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/Traits/Optimization/Process/AbstractProcessDeprecatedTrait.php b/wp-content/plugins/imagify/inc/deprecated/Traits/Optimization/Process/AbstractProcessDeprecatedTrait.php
new file mode 100644
index 00000000..679a4898
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/Traits/Optimization/Process/AbstractProcessDeprecatedTrait.php
@@ -0,0 +1,44 @@
+get_fullsize_file()' );
+
+ if ( isset( $this->file ) ) {
+ return $this->file;
+ }
+
+ $this->file = false;
+
+ if ( $this->get_media() ) {
+ $this->file = new File( $this->get_media()->get_raw_fullsize_path() );
+ }
+
+ return $this->file;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-abstract-attachment.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-abstract-attachment.php
new file mode 100644
index 00000000..0e9565cb
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-abstract-attachment.php
@@ -0,0 +1,1237 @@
+is_image()
+ */
+ protected $is_image;
+
+ /**
+ * Tell if the file is a pdf.
+ *
+ * @var bool
+ * @since 1.8
+ * @access protected
+ * @see $this->is_pdf()
+ */
+ protected $is_pdf;
+
+ /**
+ * Stores the file extension (even if the extension is not supported by Imagify).
+ *
+ * @var string|null
+ * @since 1.8
+ * @access protected
+ * @see $this->get_extension()
+ */
+ protected $extension = false;
+
+ /**
+ * Stores the file mime type + file extension (if the file is supported).
+ *
+ * @var array
+ * @since 1.8
+ * @access protected
+ * @see $this->get_file_type()
+ */
+ protected $file_type;
+
+ /**
+ * Filesystem object.
+ *
+ * @var object Imagify_Filesystem
+ * @since 1.7.1
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $filesystem;
+
+ /**
+ * The editor instances used to resize files.
+ *
+ * @var array An array of image editor objects (WP_Image_Editor_Imagick, WP_Image_Editor_GD).
+ * @since 1.7.1
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $editors = array();
+
+ /**
+ * The name of the transient that tells if optimization is processing.
+ *
+ * @var string
+ * @since 1.7.1
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $optimization_state_transient;
+
+ /**
+ * Tell if the optimization status is network-wide.
+ *
+ * @var bool
+ * @since 1.7.1
+ * @access protected
+ * @author Grégory Viguier
+ */
+ protected $optimization_state_network_wide = false;
+
+ /**
+ * The constructor.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @param int|object $id The attachment ID or the attachment itself.
+ * If an integer, make sure the attachment exists.
+ */
+ public function __construct( $id = 0 ) {
+ global $post;
+
+ if ( $id ) {
+ if ( $id instanceof WP_Post ) {
+ $this->id = $id->ID;
+ } elseif ( is_numeric( $id ) ) {
+ $this->id = $id;
+ }
+ } elseif ( $post && $id instanceof WP_Post ) {
+ $this->id = $post->ID;
+ }
+
+ $this->id = (int) $this->id;
+ $this->filesystem = Imagify_Filesystem::get_instance();
+ $this->optimization_state_transient = 'wp' !== $this->get_context() ? strtolower( $this->get_context() ) . '-' : '';
+ $this->optimization_state_transient = 'imagify-' . $this->optimization_state_transient . 'async-in-progress-' . $this->id;
+ }
+
+ /**
+ * Get the attachment context.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_context() {
+ if ( $this->context ) {
+ return $this->context;
+ }
+
+ $this->context = str_replace( array( 'Imagify_', 'Attachment' ), '', get_class( $this ) );
+ $this->context = trim( $this->context, '_' );
+ $this->context = $this->context ? $this->context : 'wp';
+
+ return $this->context;
+ }
+
+ /**
+ * Tell if the current attachment is valid.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_valid() {
+ return $this->id > 0;
+ }
+
+ /**
+ * Get the attachment ID.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return int
+ */
+ public function get_id() {
+ return $this->id;
+ }
+
+ /**
+ * Get the original attachment path.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return string
+ */
+ abstract public function get_original_path();
+
+ /**
+ * Get the original attachment URL.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return string
+ */
+ abstract public function get_original_url();
+
+ /**
+ * Get the attachment backup file path, even if the file doesn't exist.
+ *
+ * @since 1.6.13
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ abstract public function get_raw_backup_path();
+
+ /**
+ * Get the attachment backup file path.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return string|false The file path. False if it doesn't exist.
+ */
+ public function get_backup_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ $backup_path = $this->get_raw_backup_path();
+
+ if ( $backup_path && $this->filesystem->exists( $backup_path ) ) {
+ return $backup_path;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the attachment backup URL.
+ *
+ * @since 1.4
+ * @access public
+ *
+ * @return string|false
+ */
+ public function get_backup_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return get_imagify_attachment_url( $this->get_raw_backup_path() );
+ }
+
+ /**
+ * Get the attachment optimization data.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return array
+ */
+ abstract public function get_data();
+
+ /**
+ * Get the attachment optimization level.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return int
+ */
+ abstract public function get_optimization_level();
+
+ /**
+ * Get the attachment optimization status (success or error).
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return string
+ */
+ abstract public function get_status();
+
+ /**
+ * Get the attachment error if there is one.
+ *
+ * @since 1.1.5
+ * @access public
+ *
+ * @return string The message error
+ */
+ public function get_optimized_error() {
+ $error = $this->get_size_data( 'full', 'error' );
+
+ if ( is_string( $error ) ) {
+ return trim( $error );
+ }
+
+ return '';
+ }
+
+ /**
+ * Count number of optimized sizes.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return int
+ */
+ public function get_optimized_sizes_count() {
+ $data = $this->get_data();
+ $sizes = ! empty( $data['sizes'] ) && is_array( $data['sizes'] ) ? $data['sizes'] : array();
+ $count = 0;
+
+ unset( $sizes['full'] );
+
+ if ( ! $sizes ) {
+ return 0;
+ }
+
+ foreach ( $sizes as $size ) {
+ if ( ! empty( $size['success'] ) ) {
+ $count++;
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Delete the 3 metas used by Imagify.
+ *
+ * @since 1.6.6
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_imagify_data() {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ delete_post_meta( $this->id, '_imagify_data' );
+ delete_post_meta( $this->id, '_imagify_status' );
+ delete_post_meta( $this->id, '_imagify_optimization_level' );
+ }
+
+ /**
+ * Get width and height of the original image.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array
+ */
+ public function get_dimensions() {
+ return array(
+ 'width' => 0,
+ 'height' => 0,
+ );
+ }
+
+ /**
+ * Tell if the current item refers to an image, based on file extension.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool Returns false in case it's an image but not in a supported format (bmp for example).
+ */
+ public function is_image() {
+ if ( isset( $this->is_image ) ) {
+ return $this->is_image;
+ }
+
+ $this->is_image = strpos( (string) $this->get_mime_type(), 'image/' ) === 0;
+
+ return $this->is_image;
+ }
+
+ /**
+ * Tell if the current item refers to a pdf, based on file extension.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_pdf() {
+ if ( isset( $this->is_pdf ) ) {
+ return $this->is_pdf;
+ }
+
+ $this->is_pdf = 'application/pdf' === $this->get_mime_type();
+
+ return $this->is_pdf;
+ }
+
+ /**
+ * Get the file mime type.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function get_mime_type() {
+ return $this->get_file_type()->type;
+ }
+
+ /**
+ * Get the file mime type + file extension (if the file is supported).
+ *
+ * @since 1.8
+ * @access public
+ * @see wp_check_filetype()
+ * @author Grégory Viguier
+ *
+ * @return object
+ */
+ public function get_file_type() {
+ if ( isset( $this->file_type ) ) {
+ return $this->file_type;
+ }
+
+ if ( ! $this->is_valid() ) {
+ $this->file_type = (object) array(
+ 'ext' => '',
+ 'type' => '',
+ );
+ return $this->file_type;
+ }
+
+ $path = $this->get_original_path();
+
+ if ( ! $path ) {
+ $this->file_type = (object) array(
+ 'ext' => '',
+ 'type' => '',
+ );
+ return $this->file_type;
+ }
+
+ $this->file_type = (object) wp_check_filetype( $path, imagify_get_mime_types() );
+
+ return $this->file_type;
+ }
+
+ /**
+ * Get the attachment extension.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return string|null
+ */
+ public function get_extension() {
+ if ( false !== $this->extension ) {
+ return $this->extension;
+ }
+
+ if ( ! $this->is_valid() ) {
+ $this->extension = null;
+ return $this->extension;
+ }
+
+ $this->extension = $this->filesystem->path_info( $this->get_original_path(), 'extension' );
+
+ return $this->extension;
+ }
+
+ /**
+ * Tell if the current file extension is supported.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_extension_supported() {
+ return (bool) $this->get_file_type()->ext;
+ }
+
+ /**
+ * Tell if the current file mime type is supported.
+ *
+ * @since 1.6.9
+ * @since 1.8 Does the same has this->is_extension_supported().
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_mime_type_supported() {
+ return (bool) $this->get_mime_type();
+ }
+
+ /**
+ * Tell if the current attachment has the required WP metadata.
+ *
+ * @since 1.6.12
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_required_metadata() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return imagify_attachment_has_required_metadata( $this->id );
+ }
+
+ /**
+ * Get the attachment optimization level label.
+ *
+ * @since 1.2
+ * @since 1.7 Added $format parameter.
+ * @access public
+ *
+ * @param string $format Format to display the label. Use %ICON% for the icon and %s for the label.
+ * @return string
+ */
+ public function get_optimization_level_label( $format = '%s' ) {
+ return imagify_get_optimization_level_label( $this->get_optimization_level(), $format );
+ }
+
+ /**
+ * Get the original attachment size.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @param bool $human_format True to display the image human format size (1Mb).
+ * @param int $decimals Precision of number of decimal places.
+ * @return string|int
+ */
+ public function get_original_size( $human_format = true, $decimals = 2 ) {
+ if ( ! $this->is_valid() ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+ $size = $this->get_size_data( 'full', 'original_size' );
+
+ if ( ! $size ) {
+ // Check for the backup file first.
+ $filepath = $this->get_backup_path();
+
+ if ( ! $filepath ) {
+ $filepath = $this->get_original_path();
+ $filepath = $filepath && $this->filesystem->exists( $filepath ) ? $filepath : false;
+ }
+
+ $size = $filepath ? $this->filesystem->size( $filepath ) : 0;
+ }
+
+ if ( $human_format ) {
+ return imagify_size_format( (int) $size, $decimals );
+ }
+
+ return (int) $size;
+ }
+
+ /**
+ * Get the optimized attachment size.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $human_format True to display the image human format size (1Mb).
+ * @param int $decimals Precision of number of decimal places.
+ * @return string|int
+ */
+ public function get_optimized_size( $human_format = true, $decimals = 2 ) {
+ if ( ! $this->is_valid() ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+ $size = $this->get_size_data( 'full', 'optimized_size' );
+
+ if ( ! $size ) {
+ $filepath = $this->get_original_path();
+ $filepath = $filepath && $this->filesystem->exists( $filepath ) ? $filepath : false;
+ $size = $filepath ? $this->filesystem->size( $filepath ) : 0;
+ }
+
+ if ( $human_format ) {
+ return imagify_size_format( (int) $size, $decimals );
+ }
+
+ return (int) $size;
+ }
+
+ /**
+ * Get the optimized attachment size.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return float A 2-decimals float.
+ */
+ public function get_saving_percent() {
+ if ( ! $this->is_valid() ) {
+ return round( (float) 0, 2 );
+ }
+
+ $percent = $this->get_size_data( 'full', 'percent' );
+ $percent = $percent ? $percent : (float) 0;
+
+ return round( $percent, 2 );
+ }
+
+ /**
+ * Get the overall optimized size (all thumbnails).
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return float A 2-decimals float.
+ */
+ public function get_overall_saving_percent() {
+ if ( ! $this->is_valid() ) {
+ return round( (float) 0, 2 );
+ }
+
+ $percent = $this->get_data();
+ $percent = ! empty( $percent['stats']['percent'] ) ? $percent['stats']['percent'] : (float) 0;
+
+ return round( $percent, 2 );
+ }
+
+ /**
+ * Get the statistics of a specific size.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @param string $size The thumbnail slug.
+ * @param string $key The specific data slug.
+ * @return array|string
+ */
+ public function get_size_data( $size = 'full', $key = '' ) {
+ $data = $this->get_data();
+ $stats = array();
+
+ if ( isset( $data['sizes'][ $size ] ) ) {
+ $stats = $data['sizes'][ $size ];
+ }
+
+ if ( isset( $stats[ $key ] ) ) {
+ $stats = $stats[ $key ];
+ }
+
+ return $stats;
+ }
+
+ /**
+ * Get the global statistics data or a specific one.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @param string $key The specific data slug.
+ * @return array|string
+ */
+ public function get_stats_data( $key = '' ) {
+ $data = $this->get_data();
+ $stats = '';
+
+ if ( isset( $data['stats'] ) ) {
+ $stats = $data['stats'];
+ }
+
+ if ( isset( $stats[ $key ] ) ) {
+ $stats = $stats[ $key ];
+ }
+
+ return $stats;
+ }
+
+ /**
+ * Check if the attachment is already optimized (before Imagify).
+ *
+ * @since 1.1.6
+ * @access public
+ *
+ * @return bool True if the attachment is optimized.
+ */
+ public function is_already_optimized() {
+ return 'already_optimized' === $this->get_status();
+ }
+
+ /**
+ * Check if the attachment is optimized.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return bool True if the attachment is optimized.
+ */
+ public function is_optimized() {
+ return 'success' === $this->get_status();
+ }
+
+ /**
+ * Check if the attachment exceeding the limit size (> 5mo).
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return bool True if the attachment is skipped.
+ */
+ public function is_exceeded() {
+ $filepath = $this->get_original_path();
+ $size = 0;
+
+ if ( $filepath && $this->filesystem->exists( $filepath ) ) {
+ $size = $this->filesystem->size( $filepath );
+ }
+
+ return $size > IMAGIFY_MAX_BYTES;
+ }
+
+ /**
+ * Check if the attachment has a backup of the original size.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return bool True if the attachment has a backup.
+ */
+ public function has_backup() {
+ return (bool) $this->get_backup_path();
+ }
+
+ /**
+ * Check if the attachment has an error.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return bool True if the attachment has an error.
+ */
+ public function has_error() {
+ return 'error' === $this->get_status();
+ }
+
+ /**
+ * Get an image editor instance (WP_Image_Editor_Imagick, WP_Image_Editor_GD).
+ *
+ * @since 1.7.1
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param string $path A file path.
+ * @return object An image editor instance (WP_Image_Editor_Imagick, WP_Image_Editor_GD). A WP_Error object on error.
+ */
+ protected function get_editor( $path ) {
+ if ( isset( $this->editors[ $path ] ) ) {
+ return $this->editors[ $path ];
+ }
+
+ $this->editors[ $path ] = wp_get_image_editor( $path, array(
+ 'methods' => self::get_editor_methods(),
+ ) );
+
+ return $this->editors[ $path ];
+ }
+
+ /**
+ * Get the image editor methods we will use.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public static function get_editor_methods() {
+ static $methods;
+
+ if ( isset( $methods ) ) {
+ return $methods;
+ }
+
+ $methods = array(
+ 'resize',
+ 'multi_resize',
+ 'generate_filename',
+ 'save',
+ );
+
+ if ( Imagify_Filesystem::get_instance()->can_get_exif() ) {
+ $methods[] = 'rotate';
+ }
+
+ return $methods;
+ }
+
+ /**
+ * Update the metadata size of the attachment
+ *
+ * @since 1.2
+ * @access public
+ *
+ * @return void
+ */
+ abstract public function update_metadata_size();
+
+ /**
+ * Delete the backup file.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return void
+ */
+ public function delete_backup() {
+ $backup_path = $this->get_backup_path();
+
+ if ( $backup_path ) {
+ $this->filesystem->delete( $backup_path );
+ }
+ }
+
+ /**
+ * Get the registered sizes.
+ *
+ * @since 1.6.10
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array Data for the registered thumbnail sizes.
+ */
+ public static function get_registered_sizes() {
+ static $registered_sizes;
+
+ if ( ! isset( $registered_sizes ) ) {
+ $registered_sizes = get_imagify_thumbnail_sizes();
+ }
+
+ return $registered_sizes;
+ }
+
+ /**
+ * Get the unoptimized sizes for a specific attachment.
+ *
+ * @since 1.6.10
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array Data for the unoptimized thumbnail sizes.
+ * Each size data has a "file" key containing the name the thumbnail "should" have.
+ */
+ public function get_unoptimized_sizes() {
+ // The attachment must have been optimized once and have a backup.
+ if ( ! $this->is_valid() || ! $this->is_optimized() || ! $this->has_backup() || ! $this->is_image() ) {
+ return array();
+ }
+
+ $registered_sizes = self::get_registered_sizes();
+ $attachment_sizes = $this->get_data();
+ $attachment_sizes = ! empty( $attachment_sizes['sizes'] ) ? $attachment_sizes['sizes'] : array();
+ $missing_sizes = array_diff_key( $registered_sizes, $attachment_sizes );
+
+ if ( ! $missing_sizes ) {
+ // We have everything we need.
+ return array();
+ }
+
+ // Get full size dimensions.
+ $orig = wp_get_attachment_metadata( $this->id );
+ $orig_f = ! empty( $orig['file'] ) ? $orig['file'] : '';
+ $orig_w = ! empty( $orig['width'] ) ? (int) $orig['width'] : 0;
+ $orig_h = ! empty( $orig['height'] ) ? (int) $orig['height'] : 0;
+
+ if ( ! $orig_f || ! $orig_w || ! $orig_h ) {
+ return array();
+ }
+
+ $orig_f = $this->filesystem->path_info( $orig_f );
+ $orig_f = $orig_f['file_base'] . '-{%suffix%}.' . $orig_f['extension'];
+
+ // Test if the missing sizes are needed.
+ $disallowed_sizes = get_imagify_option( 'disallowed-sizes' );
+ $is_active_for_network = imagify_is_active_for_network();
+
+ foreach ( $missing_sizes as $size_name => $size_data ) {
+ $duplicate = ( $orig_w === $size_data['width'] ) && ( $orig_h === $size_data['height'] );
+
+ if ( $duplicate ) {
+ // Same dimensions as the full size.
+ unset( $missing_sizes[ $size_name ] );
+ continue;
+ }
+
+ if ( ! $is_active_for_network && isset( $disallowed_sizes[ $size_name ] ) ) {
+ // This size must be optimized.
+ unset( $missing_sizes[ $size_name ] );
+ continue;
+ }
+
+ $resize_result = image_resize_dimensions( $orig_w, $orig_h, $size_data['width'], $size_data['height'], $size_data['crop'] );
+
+ if ( ! $resize_result ) {
+ // This size is not needed.
+ unset( $missing_sizes[ $size_name ] );
+ continue;
+ }
+
+ // Provide what should be the file name.
+ list( , , , , $dst_w, $dst_h ) = $resize_result;
+ $missing_sizes[ $size_name ]['file'] = str_replace( '{%suffix%}', "{$dst_w}x{$dst_h}", $orig_f );
+ }
+
+ return $missing_sizes;
+ }
+
+ /**
+ * Fills statistics data with values from $data array.
+ *
+ * @since 1.0
+ * @since 1.6.5 Not static anymore.
+ * @since 1.6.6 Removed the attachment ID parameter.
+ * @since 1.7 Removed the image URL parameter.
+ * @access public
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param string $size The attachment size key.
+ * @return bool|array False if the original size has an error or an array contains the data for other result.
+ */
+ abstract public function fill_data( $data, $response, $size = 'full' );
+
+ /**
+ * Optimize all sizes with Imagify.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @param array $metadata The attachment meta data.
+ * @return array $optimized_data The optimization data.
+ */
+ abstract public function optimize( $optimization_level = null, $metadata = array() );
+
+ /**
+ * Optimize missing sizes with Imagify.
+ *
+ * @since 1.6.10
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @return array|object An array of thumbnail data, size by size. A WP_Error object on failure.
+ */
+ abstract public function optimize_missing_thumbnails( $optimization_level = null );
+
+ /**
+ * Re-optimize the given thumbnail sizes to the same level.
+ * Before doing this, the given sizes must be restored.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes The sizes to optimize.
+ * @return array|void A WP_Error object on failure.
+ */
+ abstract public function reoptimize_thumbnails( $sizes );
+
+ /**
+ * Process an attachment restoration from the backup file.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return void
+ */
+ abstract public function restore();
+
+ /**
+ * Resize an image if bigger than the maximum width defined in the settings.
+ *
+ * @since 1.5.7
+ * @since 1.7.1 Keys for width and height in $attachment_sizes are now 'width' and 'height' instead of 0 and 1.
+ * @access public
+ * @author Remy Perona
+ *
+ * @param string $attachment_path Path to the image.
+ * @param array $attachment_sizes Array of original image dimensions.
+ * @param int $max_width Maximum width defined in the settings.
+ * @return string Path the the resized image or the original image if the resize failed.
+ */
+ public function resize( $attachment_path, $attachment_sizes, $max_width ) {
+ if ( ! $this->is_valid() || ! $this->is_image() ) {
+ return '';
+ }
+
+ $editor = $this->get_editor( $attachment_path );
+
+ if ( is_wp_error( $editor ) ) {
+ return $editor;
+ }
+
+ $new_sizes = wp_constrain_dimensions( $attachment_sizes['width'], $attachment_sizes['height'], $max_width );
+ $image_type = strtolower( (string) $this->filesystem->path_info( $attachment_path, 'extension' ) );
+
+ // Try to correct for auto-rotation if the info is available.
+ if ( $this->filesystem->can_get_exif() && ( 'jpg' === $image_type || 'jpe' === $image_type || 'jpeg' === $image_type ) ) {
+ $exif = $this->filesystem->get_image_exif( $attachment_path );
+ $orientation = isset( $exif['Orientation'] ) ? (int) $exif['Orientation'] : 1;
+
+ switch ( $orientation ) {
+ case 3:
+ $editor->rotate( 180 );
+ break;
+ case 6:
+ $editor->rotate( -90 );
+ break;
+ case 8:
+ $editor->rotate( 90 );
+ }
+ }
+
+ // Prevent removal of the exif data when resizing (only works with Imagick).
+ add_filter( 'image_strip_meta', '__return_false', 789 );
+
+ $resized = $editor->resize( $new_sizes[0], $new_sizes[1], false );
+
+ // Remove the filter when we're done to prevent any conflict.
+ remove_filter( 'image_strip_meta', '__return_false', 789 );
+
+ if ( is_wp_error( $resized ) ) {
+ return $resized;
+ }
+
+ $resized_image_path = $editor->generate_filename( 'imagifyresized' );
+ $resized_image_saved = $editor->save( $resized_image_path );
+
+ if ( is_wp_error( $resized_image_saved ) ) {
+ return $resized_image_saved;
+ }
+
+ return $resized_image_path;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** WORKING STATUS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if the file is currently being optimized (or restored, etc).
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return bool
+ */
+ public function is_running() {
+ $callback = $this->optimization_state_network_wide ? 'get_site_transient' : 'get_transient';
+
+ return false !== call_user_func( $callback, $this->optimization_state_transient );
+ }
+
+ /**
+ * Set the running status to "running" for 10 minutes.
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function set_running_status() {
+ $callback = $this->optimization_state_network_wide ? 'set_site_transient' : 'set_transient';
+
+ call_user_func( $callback, $this->optimization_state_transient, true, 10 * MINUTE_IN_SECONDS );
+ }
+
+ /**
+ * Unset the running status.
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function delete_running_status() {
+ $callback = $this->optimization_state_network_wide ? 'delete_site_transient' : 'delete_transient';
+
+ call_user_func( $callback, $this->optimization_state_transient );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** DB ROW ================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the data row.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array
+ */
+ public function get_row() {
+ if ( isset( $this->row ) ) {
+ return $this->row;
+ }
+
+ if ( ! $this->db_class_name || ! $this->is_valid() ) {
+ return $this->invalidate_row();
+ }
+
+ $this->row = $this->get_row_db_instance()->get( $this->id );
+
+ if ( ! $this->row ) {
+ return $this->invalidate_row();
+ }
+
+ return $this->row;
+ }
+
+ /**
+ * Update the data row.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @param array $data The data to update.
+ */
+ public function update_row( $data ) {
+ if ( ! $this->db_class_name || ! $this->is_valid() ) {
+ return;
+ }
+
+ $this->get_row_db_instance()->update( $this->id, $data );
+
+ $this->reset_row_cache();
+ }
+
+ /**
+ * Delete the data row.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ */
+ public function delete_row() {
+ if ( ! $this->db_class_name || ! $this->is_valid() ) {
+ return;
+ }
+
+ $this->get_row_db_instance()->delete( $this->id );
+
+ $this->invalidate_row();
+ }
+
+ /**
+ * Shorthand to get the DB table instance.
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return object The DB table instance.
+ */
+ public function get_row_db_instance() {
+ return call_user_func( array( $this->db_class_name, 'get_instance' ) );
+ }
+
+ /**
+ * Invalidate the row.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array The row
+ */
+ public function invalidate_row() {
+ $this->row = array();
+ return $this->row;
+ }
+
+ /**
+ * Reset the row cache.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return null The row.
+ */
+ public function reset_row_cache() {
+ $this->row = null;
+ return $this->row;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-abstract-db-deprecated.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-abstract-db-deprecated.php
new file mode 100644
index 00000000..9ee4a61e
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-abstract-db-deprecated.php
@@ -0,0 +1,48 @@
+imagify_auto_optimize_callback()' );
+
+ if ( empty( $_POST['_ajax_nonce'] ) || empty( $_POST['attachment_id'] ) || empty( $_POST['metadata'] ) || empty( $_POST['context'] ) ) { // WPCS: CSRF ok.
+ return;
+ }
+
+ $context = imagify_sanitize_context( $_POST['context'] );
+ $attachment_id = absint( $_POST['attachment_id'] );
+
+ imagify_check_nonce( 'new_media-' . $attachment_id );
+ imagify_check_user_capacity( 'auto-optimize' );
+
+ $attachment = get_imagify_attachment( $context, $attachment_id, 'imagify_async_optimize_upload_new_media' );
+
+ // Optimize it!!!!!
+ $attachment->optimize( null, $_POST['metadata'] );
+ die( 1 );
+ }
+
+ /**
+ * Optimize image on picture editing (resize, crop...) with async request.
+ *
+ * @since 1.6.11
+ * @since 1.8.4 Deprecated
+ * @access public
+ * @author Julio Potier
+ * @deprecated
+ */
+ public function imagify_async_optimize_save_image_editor_file_callback() {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.8.4', 'Imagify_Admin_Ajax_Post::get_instance()->imagify_auto_optimize_callback()' );
+
+ $attachment_id = ! empty( $_POST['postid'] ) ? absint( $_POST['postid'] ) : 0;
+
+ if ( ! $attachment_id || empty( $_POST['do'] ) ) {
+ return;
+ }
+
+ imagify_check_nonce( 'image_editor-' . $attachment_id );
+ imagify_check_user_capacity( 'edit_post', $attachment_id );
+
+ $attachment = get_imagify_attachment( 'wp', $attachment_id, 'wp_ajax_imagify_async_optimize_save_image_editor_file' );
+
+ if ( ! $attachment->get_data() ) {
+ return;
+ }
+
+ $optimization_level = $attachment->get_optimization_level();
+ $metadata = wp_get_attachment_metadata( $attachment_id );
+
+ // Remove old optimization data.
+ $attachment->delete_imagify_data();
+
+ if ( 'restore' === $_POST['do'] ) {
+ // Restore the backup file.
+ $attachment->restore();
+
+ // Get old metadata to regenerate all thumbnails.
+ $metadata = array( 'sizes' => array() );
+ $backup_sizes = (array) get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
+
+ foreach ( $backup_sizes as $size_key => $size_data ) {
+ $size_key = str_replace( '-origin', '' , $size_key );
+ $metadata['sizes'][ $size_key ] = $size_data;
+ }
+ }
+
+ // Optimize it!!!!!
+ $attachment->optimize( $optimization_level, $metadata );
+ die( 1 );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** CUSTOM FOLDERS CALLBACKS ================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Optimize a file.
+ *
+ * @since 1.7
+ * @since 1.9 Deprecated
+ * @access public
+ * @author Grégory Viguier
+ * @deprecated
+ */
+ public function imagify_bulk_optimize_file_callback() {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9', '$this->imagify_bulk_optimize_callback()' );
+
+ imagify_check_nonce( 'imagify-bulk-upload' );
+ imagify_check_user_capacity( 'optimize-file' );
+
+ $file_id = filter_input( INPUT_POST, 'image', FILTER_VALIDATE_INT );
+ $context = imagify_sanitize_context( filter_input( INPUT_POST, 'context', FILTER_SANITIZE_STRING ) );
+ $context = ! $context || 'wp' === strtolower( $context ) ? 'File' : $context;
+
+ if ( ! $file_id ) {
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ $file = get_imagify_attachment( $context, $file_id, 'imagify_bulk_optimize_file' );
+
+ if ( ! $file->is_valid() ) {
+ imagify_die( __( 'Invalid file ID', 'imagify' ) );
+ }
+
+ // Restore before re-optimizing.
+ if ( false !== $file->get_optimization_level() ) {
+ $file->restore();
+ }
+
+ // Optimize it.
+ $result = $file->optimize( $this->get_optimization_level() );
+
+ // Return the optimization statistics.
+ if ( ! $file->is_optimized() ) {
+ $data = array(
+ 'success' => false,
+ 'error_code' => '',
+ 'error' => (string) $file->get_optimized_error(),
+ );
+
+ if ( ! $file->has_error() ) {
+ $data['error_code'] = 'already-optimized';
+ } else {
+ $message = 'You\'ve consumed all your data. You have to upgrade your account to continue';
+
+ if ( $data['error'] === $message ) {
+ $data['error_code'] = 'over-quota';
+ }
+ }
+
+ $data['error'] = imagify_translate_api_message( $data['error'] );
+
+ imagify_die( $data );
+ }
+
+ $data = $file->get_size_data();
+
+ wp_send_json_success( array(
+ 'success' => true,
+ 'original_size_human' => imagify_size_format( $data['original_size'], 2 ),
+ 'new_size_human' => imagify_size_format( $data['optimized_size'], 2 ),
+ 'overall_saving' => $data['original_size'] - $data['optimized_size'],
+ 'overall_saving_human' => imagify_size_format( $data['original_size'] - $data['optimized_size'], 2 ),
+ 'original_overall_size' => $data['original_size'],
+ 'original_overall_size_human' => imagify_size_format( $data['original_size'], 2 ),
+ 'new_overall_size' => $data['optimized_size'],
+ 'percent_human' => $data['percent'] . '%',
+ 'thumbnails' => $file->get_optimized_sizes_count(),
+ ) );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** AUTOMATIC OPTIMIZATION ================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Auto-optimize files.
+ *
+ * @since 1.8.4
+ * @since 1.9 Deprecated
+ * @access public
+ * @author Grégory Viguier
+ * @see Imagify_Auto_Optimization->do_auto_optimization()
+ * @deprecated
+ */
+ public function imagify_auto_optimize_callback() {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9' );
+
+ if ( empty( $_POST['_ajax_nonce'] ) || empty( $_POST['attachment_id'] ) || empty( $_POST['context'] ) ) { // WPCS: CSRF ok.
+ imagify_die( __( 'Invalid request', 'imagify' ) );
+ }
+
+ $media_id = $this->get_media_id( 'POST' );
+
+ imagify_check_nonce( 'imagify_auto_optimize-' . $media_id );
+
+ if ( ! get_transient( 'imagify-auto-optimize-' . $media_id ) ) {
+ imagify_die();
+ }
+
+ delete_transient( 'imagify-auto-optimize-' . $media_id );
+
+ $context = $this->get_context( 'POST' );
+ $process = imagify_get_optimization_process( $media_id, $context );
+
+ if ( ! $process->is_valid() ) {
+ imagify_die( __( 'This media is not valid.', 'imagify' ) );
+ }
+
+ if ( ! $process->get_media()->is_supported() ) {
+ imagify_die( __( 'This type of file is not supported.', 'imagify' ) );
+ }
+
+ $this->check_can_optimize();
+
+ /**
+ * Let's start.
+ */
+ $is_new_upload = ! empty( $_POST['is_new_upload'] );
+
+ /**
+ * Triggered before a media is auto-optimized.
+ *
+ * @since 1.8.4
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param bool $is_new_upload True if it's a new upload. False otherwize.
+ */
+ do_action( 'imagify_before_auto_optimization', $media_id, $is_new_upload );
+
+ if ( $is_new_upload ) {
+ /**
+ * It's a new upload.
+ */
+ // Optimize.
+ $process->optimize();
+ } else {
+ /**
+ * The media has already been optimized (or at least it has been tried).
+ */
+ $data = $process->get_data();
+
+ // Get the optimization level before deleting the optimization data.
+ $optimization_level = $data->get_optimization_level();
+
+ // Remove old optimization data.
+ $data->delete_imagify_data();
+
+ // Some specifics for the image editor.
+ if ( ! empty( $_POST['data']['do'] ) && 'restore' === $_POST['data']['do'] ) {
+ // Restore the backup file.
+ $process->restore();
+ }
+
+ // Optimize.
+ $process->optimize( $optimization_level );
+ }
+
+ /**
+ * Triggered after a media is auto-optimized.
+ *
+ * @since 1.8.4
+ * @author Grégory Viguier
+ *
+ * @param int $media_id The media ID.
+ * @param bool $is_new_upload True if it's a new upload. False otherwize.
+ */
+ do_action( 'imagify_after_auto_optimization', $media_id, $is_new_upload );
+ die( 1 );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** VARIOUS FOR OPTIMIZATION ================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get all unoptimized attachment ids.
+ *
+ * @since 1.6.11
+ * @since 1.9 Deprecated
+ * @access public
+ * @author Jonathan Buttigieg
+ * @deprecated
+ */
+ public function imagify_get_unoptimized_attachment_ids_callback() {
+ global $wpdb;
+
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9', '$this->imagify_get_media_ids_callback()' );
+
+ imagify_check_nonce( 'imagify-bulk-upload' );
+ imagify_check_user_capacity( 'bulk-optimize' );
+ $this->check_can_optimize();
+
+ @set_time_limit( 0 );
+
+ // Get (ordered) IDs.
+ $optimization_level = $this->get_optimization_level();
+
+ $mime_types = Imagify_DB::get_mime_types();
+ $statuses = Imagify_DB::get_post_statuses();
+ $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause();
+ $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause( array(
+ 'prepared' => true,
+ ) );
+ $ids = $wpdb->get_col( $wpdb->prepare( // WPCS: unprepared SQL ok.
+ "
+ SELECT p.ID
+ FROM $wpdb->posts AS p
+ $nodata_join
+ LEFT JOIN $wpdb->postmeta AS mt1
+ ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
+ LEFT JOIN $wpdb->postmeta AS mt2
+ ON ( p.ID = mt2.post_id AND mt2.meta_key = '_imagify_optimization_level' )
+ WHERE
+ p.post_mime_type IN ( $mime_types )
+ AND (
+ mt1.meta_value = 'error'
+ OR
+ mt2.meta_value != %d
+ OR
+ mt2.post_id IS NULL
+ )
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ $nodata_where
+ GROUP BY p.ID
+ ORDER BY
+ CASE mt1.meta_value
+ WHEN 'already_optimized' THEN 2
+ ELSE 1
+ END ASC,
+ p.ID DESC
+ LIMIT 0, %d",
+ $optimization_level,
+ imagify_get_unoptimized_attachment_limit()
+ ) );
+
+ $wpdb->flush();
+ unset( $mime_types );
+ $ids = array_filter( array_map( 'absint', $ids ) );
+
+ if ( ! $ids ) {
+ wp_send_json_success( array() );
+ }
+
+ $results = Imagify_DB::get_metas( array(
+ // Get attachments filename.
+ 'filenames' => '_wp_attached_file',
+ // Get attachments data.
+ 'data' => '_imagify_data',
+ // Get attachments optimization level.
+ 'optimization_levels' => '_imagify_optimization_level',
+ // Get attachments status.
+ 'statuses' => '_imagify_status',
+ ), $ids );
+
+ // First run.
+ foreach ( $ids as $i => $id ) {
+ $attachment_status = isset( $results['statuses'][ $id ] ) ? $results['statuses'][ $id ] : false;
+ $attachment_optimization_level = isset( $results['optimization_levels'][ $id ] ) ? $results['optimization_levels'][ $id ] : false;
+ $attachment_error = '';
+
+ if ( isset( $results['data'][ $id ]['sizes']['full']['error'] ) ) {
+ $attachment_error = $results['data'][ $id ]['sizes']['full']['error'];
+ }
+
+ // Don't try to re-optimize if the optimization level is still the same.
+ if ( $optimization_level === $attachment_optimization_level && is_string( $attachment_error ) ) {
+ unset( $ids[ $i ] );
+ continue;
+ }
+
+ // Don't try to re-optimize images already compressed.
+ if ( 'already_optimized' === $attachment_status && $attachment_optimization_level >= $optimization_level ) {
+ unset( $ids[ $i ] );
+ continue;
+ }
+
+ $attachment_error = trim( $attachment_error );
+
+ // Don't try to re-optimize images with an empty error message.
+ if ( 'error' === $attachment_status && empty( $attachment_error ) ) {
+ unset( $ids[ $i ] );
+ }
+ }
+
+ if ( ! $ids ) {
+ wp_send_json_success( array() );
+ }
+
+ $ids = array_values( $ids );
+
+ /**
+ * Triggered before testing for file existence.
+ *
+ * @since 1.6.7
+ * @author Grégory Viguier
+ *
+ * @param array $ids An array of attachment IDs.
+ * @param array $results An array of the data fetched from the database.
+ * @param int $optimization_level The optimization level that will be used for the optimization.
+ */
+ do_action( 'imagify_bulk_optimize_before_file_existence_tests', $ids, $results, $optimization_level );
+
+ $data = array();
+
+ foreach ( $ids as $i => $id ) {
+ if ( empty( $results['filenames'][ $id ] ) ) {
+ // Problem.
+ continue;
+ }
+
+ $file_path = get_imagify_attached_file( $results['filenames'][ $id ] );
+
+ /** This filter is documented in inc/deprecated/deprecated.php. */
+ $file_path = apply_filters( 'imagify_file_path', $file_path );
+
+ if ( ! $file_path || ! $this->filesystem->exists( $file_path ) ) {
+ continue;
+ }
+
+ $attachment_backup_path = get_imagify_attachment_backup_path( $file_path );
+ $attachment_status = isset( $results['statuses'][ $id ] ) ? $results['statuses'][ $id ] : false;
+ $attachment_optimization_level = isset( $results['optimization_levels'][ $id ] ) ? $results['optimization_levels'][ $id ] : false;
+
+ // Don't try to re-optimize if there is no backup file.
+ if ( 'success' === $attachment_status && $optimization_level !== $attachment_optimization_level && ! $this->filesystem->exists( $attachment_backup_path ) ) {
+ continue;
+ }
+
+ $data[ '_' . $id ] = get_imagify_attachment_url( $results['filenames'][ $id ] );
+ } // End foreach().
+
+ if ( ! $data ) {
+ wp_send_json_success( array() );
+ }
+
+ wp_send_json_success( $data );
+ }
+
+ /**
+ * Get all unoptimized file ids.
+ *
+ * @since 1.7
+ * @since 1.9 Deprecated
+ * @access public
+ * @author Grégory Viguier
+ * @deprecated
+ */
+ public function imagify_get_unoptimized_file_ids_callback() {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9', '$this->imagify_get_media_ids_callback()' );
+
+ imagify_check_nonce( 'imagify-bulk-upload' );
+ imagify_check_user_capacity( 'optimize-file' );
+
+ $this->check_can_optimize();
+
+ @set_time_limit( 0 );
+
+ $optimization_level = $this->get_optimization_level();
+
+ /**
+ * Get the folders from DB.
+ */
+ $folders = Imagify_Custom_Folders::get_folders( array(
+ 'active' => true,
+ ) );
+
+ if ( ! $folders ) {
+ wp_send_json_success( array() );
+ }
+
+ /**
+ * Triggered before getting file IDs.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $folders An array of folders data.
+ * @param int $optimization_level The optimization level that will be used for the optimization.
+ */
+ do_action( 'imagify_bulk_optimize_files_before_get_files', $folders, $optimization_level );
+
+ /**
+ * Get the files from DB, and from the folders.
+ */
+ $files = Imagify_Custom_Folders::get_files_from_folders( $folders, array(
+ 'optimization_level' => $optimization_level,
+ ) );
+
+ if ( ! $files ) {
+ wp_send_json_success( array() );
+ }
+
+ // We need to output file URLs.
+ foreach ( $files as $k => $file ) {
+ $files[ $k ] = Imagify_Files_Scan::remove_placeholder( $file['path'], 'url' );
+ }
+
+ wp_send_json_success( $files );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-as3cf-attachment.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-as3cf-attachment.php
new file mode 100644
index 00000000..5bd60182
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-as3cf-attachment.php
@@ -0,0 +1,990 @@
+get_thumbnail_path();
+ }
+
+ /**
+ * Get a thumbnail path.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param string $size_file The basename of the file. If not provided, the path to the main file is returned.
+ * @return string|bool Path to the file if it exists or has been successfully retrieved from S3. False on failure.
+ */
+ public function get_thumbnail_path( $size_file = false ) {
+ if ( ! $this->is_valid() ) {
+ return '';
+ }
+
+ $file_path = get_attached_file( $this->id, true );
+
+ if ( $size_file ) {
+ // It's not the full size.
+ $file_path = $this->filesystem->dir_path( $file_path ) . $size_file;
+ }
+
+ return $file_path;
+ }
+
+ /**
+ * Get the original attachment URL.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @return string|bool The main file URL. False on failure.
+ */
+ public function get_original_url() {
+ return $this->get_thumbnail_url();
+ }
+
+ /**
+ * Get a thumbnail URL.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param string $size_file The basename of the file. If not provided, the main file's URL is returned.
+ * @return string|bool The file URL. False on failure.
+ */
+ public function get_thumbnail_url( $size_file = false ) {
+ if ( ! $this->is_extension_supported() ) {
+ return false;
+ }
+
+ $file_url = wp_get_attachment_url( $this->id );
+
+ if ( $size_file ) {
+ // It's not the full size.
+ $file_url = $this->filesystem->dir_path( $file_url ) . $size_file;
+ }
+
+ return $file_url;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** THE PUBLIC STUFF ======================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Optimize all sizes with Imagify.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (2 = ultra, 1 = aggressive, 0 = normal).
+ * @param array $metadata The attachment meta data, containing the sizes. Provide only for a new attachment.
+ * @return array|bool The optimization data. False on failure.
+ */
+ public function optimize( $optimization_level = null, $metadata = array() ) {
+ $metadata_changed = false;
+
+ /**
+ * Make some sanity tests first.
+ */
+
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return false;
+ }
+
+ // To avoid issue with "original_size" at 0 in "_imagify_data".
+ if ( 0 === (int) $this->get_stats_data( 'original_size' ) ) {
+ $this->delete_imagify_data();
+ }
+
+ $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
+
+ // Check if the full size is already optimized with this level.
+ if ( $this->is_optimized() && $this->get_optimization_level() === $optimization_level ) {
+ return false;
+ }
+
+ // Get file path of the full size.
+ $attachment_path = $this->get_original_path();
+ $attachment_url = $this->get_original_url();
+
+ if ( ! $attachment_path ) {
+ // We're in deep sh**.
+ return false;
+ }
+
+ if ( ! $this->filesystem->exists( $attachment_path ) && ! $this->get_file_from_s3( $attachment_path ) ) {
+ // The file doesn't exist and couldn't be retrieved from S3.
+ return false;
+ }
+
+ /**
+ * Start the process.
+ */
+ $this->set_running_status();
+
+ /** This hook is documented in /inc/classes/class-imagify-attachment.php. */
+ do_action( 'before_imagify_optimize_attachment', $this->id );
+
+ $metadata = $this->set_deletion_status( $metadata );
+
+ // Store the paths of the files that may be deleted once optimized and sent to S3.
+ $to_delete = array();
+ $filesize_total = 0;
+
+ // Maybe resize (and backup) the image.
+ $resized = $this->is_image() && $this->maybe_resize( $attachment_path );
+
+ if ( $resized ) {
+ $size = $this->filesystem->get_image_size( $attachment_path );
+
+ if ( $size ) {
+ $metadata['width'] = $size['width'];
+ $metadata['height'] = $size['height'];
+ $metadata_changed = true;
+ }
+ }
+
+ // Optimize the full size.
+ $response = do_imagify( $attachment_path, array(
+ 'optimization_level' => $optimization_level,
+ 'context' => 'wp',
+ 'resized' => $resized,
+ 'original_size' => $this->get_original_size( false ),
+ ) );
+
+ $data = $this->fill_data( null, $response );
+
+ if ( $this->delete_files ) {
+ $to_delete[] = $attachment_path;
+ // This is used by AS3CF.
+ $bytes = $this->filesystem->size( $attachment_path );
+
+ if ( false !== $bytes ) {
+ $metadata_changed = true;
+ $filesize_total += $bytes;
+ $metadata['filesize'] = $bytes;
+ } else {
+ $metadata['filesize'] = 0;
+ }
+ }
+
+ /** This filter is documented in /inc/classes/class-imagify-attachment.php. */
+ $data = apply_filters( 'imagify_fill_full_size_data', $data, $response, $this->id, $attachment_path, $attachment_url, 'full', $optimization_level, $metadata );
+
+ // Save the optimization level.
+ update_post_meta( $this->id, '_imagify_optimization_level', $optimization_level );
+
+ if ( ! $data ) {
+ // The optimization failed.
+ $metadata = $metadata_changed ? $metadata : false;
+ $this->cleanup( $metadata, $to_delete );
+ return false;
+ }
+
+ // Optimize all thumbnails.
+ if ( ! empty( $metadata['sizes'] ) ) {
+ $disallowed_sizes = get_imagify_option( 'disallowed-sizes' );
+ $is_active_for_network = imagify_is_active_for_network();
+
+ foreach ( $metadata['sizes'] as $size_key => $size_data ) {
+ $thumbnail_path = $this->get_thumbnail_path( $size_data['file'] );
+ $thumbnail_url = $this->get_thumbnail_url( $size_data['file'] );
+
+ if ( $this->delete_files ) {
+ $to_delete[] = $thumbnail_path;
+
+ // Even if this size must not be optimized ($disallowed_sizes), we must fetch the file from S3 to get its size, and not to trigger a `WP_Error` in `upload_attachment_to_s3()`.
+ if ( ! $this->filesystem->exists( $thumbnail_path ) && ! $this->get_file_from_s3( $thumbnail_path ) ) {
+ // Doesn't exist and couldn't be retrieved from S3.
+ $data['sizes'][ $size_key ] = array(
+ 'success' => false,
+ 'error' => __( 'This size could not be retrieved from Amazon S3.', 'imagify' ),
+ );
+ continue;
+ }
+
+ // This is used by AS3CF.
+ $bytes = $this->filesystem->size( $thumbnail_path );
+
+ if ( false !== $bytes ) {
+ $filesize_total += $bytes;
+ }
+ }
+
+ // Check if this size has to be optimized.
+ if ( ! $is_active_for_network && isset( $disallowed_sizes[ $size_key ] ) && $this->is_image() ) {
+ $data['sizes'][ $size_key ] = array(
+ 'success' => false,
+ 'error' => __( 'This size is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
+ );
+
+ /** This filter is documented in /inc/classes/class-imagify-attachment.php. */
+ $data = apply_filters( 'imagify_fill_unauthorized_thumbnail_data', $data, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata );
+ continue;
+ }
+
+ if ( ! $this->delete_files && ! $this->filesystem->exists( $thumbnail_path ) && ! $this->get_file_from_s3( $thumbnail_path ) ) {
+ // Doesn't exist and couldn't be retrieved from S3.
+ $data['sizes'][ $size_key ] = array(
+ 'success' => false,
+ 'error' => __( 'This size could not be retrieved from Amazon S3.', 'imagify' ),
+ );
+ continue;
+ }
+
+ if ( ! $this->is_image() ) {
+ continue;
+ }
+
+ // Optimize the thumbnail size.
+ $response = do_imagify( $thumbnail_path, array(
+ 'backup' => false,
+ 'optimization_level' => $optimization_level,
+ 'context' => 'wp',
+ ) );
+
+ $data = $this->fill_data( $data, $response, $size_key );
+
+ /** This filter is documented in /inc/classes/class-imagify-attachment.php. */
+ $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata );
+ } // End foreach().
+ } // End if().
+
+ $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 );
+
+ update_post_meta( $this->id, '_imagify_data', $data );
+ update_post_meta( $this->id, '_imagify_status', 'success' );
+
+ if ( $this->delete_files && $filesize_total ) {
+ // Add the total file size for all image sizes. This is a meta used by AS3CF.
+ update_post_meta( $this->id, 'wpos3_filesize_total', $filesize_total );
+ }
+
+ $optimized_data = $this->get_data();
+
+ /** This hook is documented in /inc/classes/class-imagify-attachment.php. */
+ do_action( 'after_imagify_optimize_attachment', $this->id, $optimized_data );
+
+ $sent = $this->maybe_send_attachment_to_s3( $metadata, $attachment_path );
+ // Update metadata only if they changed.
+ $metadata = $metadata_changed ? $metadata : false;
+ // Delete files only if they have been uploaded to S3.
+ $to_delete = $sent ? $to_delete : array();
+
+ $this->cleanup( $metadata, $to_delete );
+
+ return $optimized_data;
+ }
+
+ /**
+ * Optimize missing thumbnail sizes with Imagify.
+ *
+ * @since 1.6.10
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @return array|object An array of thumbnail data, size by size. A WP_Error object on failure.
+ */
+ public function optimize_missing_thumbnails( $optimization_level = null ) {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() || ! $this->is_image() ) {
+ return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
+ }
+
+ /**
+ * Create missing thumbnails and optimize them.
+ */
+ $result = parent::optimize_missing_thumbnails( $optimization_level );
+ $result_sizes = array();
+
+ $this->set_running_status();
+
+ if ( is_array( $result ) ) {
+ // All good.
+ $result_sizes = $result;
+ } elseif ( is_wp_error( $result ) ) {
+ // Some thumbnails could not be created. Lets see if some were.
+ $result_sizes = $result->get_error_data( 'image_resize_error' );
+ $result_sizes = ! empty( $result_sizes['sizes_succeeded'] ) ? $result_sizes['sizes_succeeded'] : array();
+ }
+
+ if ( ! $result_sizes ) {
+ // No thumbnails created.
+ $this->delete_running_status();
+ return $result;
+ }
+
+ /**
+ * Fetch all images from S3 if they're not on the server.
+ * S3 Offload needs ALL images (so it can update its metas), we can't just send some of them without entering Hell -_-'.
+ */
+ $metadata = $this->set_deletion_status();
+
+ if ( ! $this->can_send_to_s3() ) {
+ // The other thumbnails are not on S3, so we don't need to send the new ones.
+ $this->delete_running_status();
+ return $result;
+ }
+
+ /**
+ * The main file.
+ */
+ $attachment_path = $this->get_original_path();
+ $to_delete = array();
+ $to_skip = array();
+ $filesize_total = 0;
+ $metadata_changed = false;
+
+ if ( ! $attachment_path ) {
+ // WAT?!
+ if ( ! is_wp_error( $result ) ) {
+ $result = new WP_Error( 'no_attachment_path', __( 'Files could not be sent to Amazon S3.', 'imagify' ), array(
+ 'sizes_succeeded' => $result_sizes,
+ ) );
+ } else {
+ $result->add( 'no_attachment_path', __( 'Files could not be sent to Amazon S3.', 'imagify' ) );
+ }
+
+ $this->delete_running_status();
+ return $result;
+ }
+
+ if ( ! $this->filesystem->exists( $attachment_path ) && ! $this->get_file_from_s3( $attachment_path ) ) {
+ // The file doesn't exist and couldn't be retrieved from S3.
+ if ( ! is_wp_error( $result ) ) {
+ $result = new WP_Error( 'main_file_not_on_s3', __( 'The main image could not be retrieved from Amazon S3.', 'imagify' ), array(
+ 'sizes_succeeded' => $result_sizes,
+ ) );
+ } else {
+ $result->add( 'main_file_not_on_s3', __( 'The main image could not be retrieved from Amazon S3.', 'imagify' ) );
+ }
+
+ $this->delete_running_status();
+ return $result;
+ }
+
+ // Files that must not be retrieved from S3.
+ foreach ( $result_sizes as $size_key => $size_data ) {
+ $to_skip[] = $this->get_thumbnail_path( $size_data['file'] );
+ }
+
+ // Store the paths of the files that may be deleted once sent to S3.
+ if ( $this->delete_files ) {
+ $to_delete[] = $attachment_path;
+ $to_delete = array_merge( $to_delete, $to_skip );
+
+ // This is used by AS3CF.
+ $bytes = $this->filesystem->size( $attachment_path );
+
+ if ( false !== $bytes ) {
+ $metadata_changed = true;
+ $filesize_total += $bytes;
+ $metadata['filesize'] = $bytes;
+ } elseif ( ! isset( $metadata['filesize'] ) ) {
+ $metadata_changed = true;
+ $metadata['filesize'] = 0;
+ }
+ }
+
+ /**
+ * The thumbnails.
+ */
+ if ( ! empty( $metadata['sizes'] ) ) {
+ $to_skip = array_flip( $to_skip );
+
+ foreach ( $metadata['sizes'] as $size_key => $size_data ) {
+ $thumbnail_path = $this->get_thumbnail_path( $size_data['file'] );
+
+ if ( isset( $to_skip[ $thumbnail_path ] ) ) {
+ continue;
+ }
+
+ if ( ! $this->filesystem->exists( $thumbnail_path ) && ! $this->get_file_from_s3( $thumbnail_path ) ) {
+ // The file doesn't exist and couldn't be retrieved from S3.
+ if ( ! is_wp_error( $result ) ) {
+ $result = new WP_Error( 'thumbnail_not_on_s3', __( 'This size could not be retrieved from Amazon S3.', 'imagify' ), array(
+ 'sizes_succeeded' => $result_sizes,
+ 'size' => $size_key,
+ ) );
+ } else {
+ $result->add( 'thumbnail_not_on_s3', __( 'This size could not be retrieved from Amazon S3.', 'imagify' ), array(
+ 'size' => $size_key,
+ ) );
+ }
+
+ $this->delete_running_status();
+ return $result;
+ }
+
+ if ( $this->delete_files ) {
+ $to_delete[] = $thumbnail_path;
+
+ // This is used by AS3CF.
+ $bytes = $this->filesystem->size( $thumbnail_path );
+
+ if ( false !== $bytes ) {
+ $filesize_total += $bytes;
+ }
+ }
+ } // End foreach().
+ } // End if().
+
+ if ( $this->delete_files && $filesize_total ) {
+ // Add the total file size for all image sizes. This is a meta used by AS3CF.
+ update_post_meta( $this->id, 'wpos3_filesize_total', $filesize_total );
+ }
+
+ $sent = $this->maybe_send_attachment_to_s3( $metadata, $attachment_path );
+ // Update metadata only if they changed.
+ $metadata = $metadata_changed ? $metadata : false;
+ // Delete files only if they have been uploaded to S3.
+ $to_delete = $sent ? $to_delete : array();
+
+ $this->cleanup( $metadata, $to_delete );
+
+ return $result;
+ }
+
+ /**
+ * Re-optimize the given thumbnail sizes to the same level.
+ * Not supported yet in this context.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes The sizes to optimize.
+ * @return array|void A WP_Error object on failure.
+ */
+ public function reoptimize_thumbnails( $sizes ) {}
+
+ /**
+ * Process an attachment restoration from the backup file.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @return array A list of files sent to S3.
+ */
+ public function restore() {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return false;
+ }
+
+ // Stop the process if there is no backup file to restore.
+ if ( ! $this->has_backup() ) {
+ return false;
+ }
+
+ /** This hook is documented in /inc/classes/class-imagify-attachment.php. */
+ do_action( 'before_imagify_restore_attachment', $this->id );
+
+ $backup_path = $this->get_backup_path();
+ $attachment_path = $this->get_original_path();
+
+ if ( ! $attachment_path ) {
+ return false;
+ }
+
+ // Create the original image from the backup.
+ $this->filesystem->copy( $backup_path, $attachment_path, true );
+ $this->filesystem->chmod_file( $attachment_path );
+
+ if ( ! $this->filesystem->exists( $attachment_path ) ) {
+ return false;
+ }
+
+ $this->set_deletion_status();
+
+ // Remove old optimization data.
+ $this->delete_imagify_data();
+
+ if ( ! $this->is_image() ) {
+ // We need to delete the thumbnails for pdf, or new ones will be generated with new unique file names.
+ $metadata = wp_get_attachment_metadata( $this->id );
+
+ if ( ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) {
+ foreach ( $metadata['sizes'] as $size_key => $size_data ) {
+ $thumbnail_path = $this->get_thumbnail_path( $size_data['file'] );
+
+ if ( $this->filesystem->exists( $thumbnail_path ) ) {
+ $this->filesystem->delete( $thumbnail_path );
+ }
+ }
+ }
+ }
+
+ if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/image.php';
+ }
+
+ // Generate new thumbnails and new metadata.
+ $metadata = wp_generate_attachment_metadata( $this->id, $attachment_path );
+
+ // Send to S3.
+ $sent = $this->maybe_send_attachment_to_s3( $metadata, $attachment_path );
+ // Files restored (and maybe to delete).
+ $files = array();
+ // If the files must be deleted, we need to store the file sizes.
+ $filesize_total = 0;
+
+ if ( $sent ) {
+ $files[] = $attachment_path;
+ }
+
+ if ( $this->delete_files ) {
+ // This is used by AS3CF.
+ $bytes = $this->filesystem->size( $attachment_path );
+
+ if ( false !== $bytes ) {
+ $filesize_total += $bytes;
+ $metadata['filesize'] = $bytes;
+ } else {
+ $metadata['filesize'] = 0;
+ }
+ }
+
+ if ( ! empty( $metadata['sizes'] ) && ( $sent || $this->delete_files ) ) {
+ foreach ( $metadata['sizes'] as $size_key => $size_data ) {
+ $thumbnail_path = $this->get_thumbnail_path( $size_data['file'] );
+
+ if ( $sent ) {
+ $files[] = $thumbnail_path;
+ }
+
+ if ( $this->delete_files ) {
+ // This is used by AS3CF.
+ $bytes = $this->filesystem->size( $thumbnail_path );
+
+ if ( false !== $bytes ) {
+ $filesize_total += $bytes;
+ }
+ }
+ }
+ }
+
+ if ( $this->delete_files && $filesize_total ) {
+ // Add the total file size for all image sizes. This is a meta used by AS3CF.
+ update_post_meta( $this->id, 'wpos3_filesize_total', $filesize_total );
+ }
+
+ /** This hook is documented in /inc/classes/class-imagify-attachment.php. */
+ do_action( 'after_imagify_restore_attachment', $this->id );
+
+ $to_delete = $this->delete_files ? $files : array();
+
+ $this->cleanup( $metadata, $to_delete );
+
+ return $files;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** INTERNAL UTILITIES ====================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Cleanup after optimization or a restore:
+ * - Maybe update metadata.
+ * - Maybe delete local files.
+ * - Delete the "optimization status" transient.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param array $new_metadata New attachment metadata to be stored.
+ * @param array $files_to_remove Files to delete.
+ */
+ protected function cleanup( $new_metadata, $files_to_remove ) {
+ if ( $new_metadata ) {
+ /**
+ * Filter the metadata stored after optimization or a restore.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param array $new_metadata New attachment metadata to be stored.
+ * @param int $id The attachment ID.
+ */
+ $new_metadata = apply_filters( 'imagify_as3cf_attachment_cleanup_metadata', $new_metadata, $this->id );
+ /**
+ * Update the attachment meta that contains the file sizes.
+ * Here we don't use wp_update_attachment_metadata() to prevent triggering unwanted hooks.
+ */
+ update_post_meta( $this->id, '_wp_attachment_metadata', $new_metadata );
+ }
+
+ if ( $files_to_remove ) {
+ $attachment_path = $this->get_original_path();
+ /** This filter is documented in /amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php. */
+ $files_to_remove = (array) apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $this->id, $attachment_path );
+ $files_to_remove = array_filter( $files_to_remove );
+
+ if ( $files_to_remove ) {
+ $files_to_remove = array_unique( $files_to_remove );
+ /**
+ * Delete the local files.
+ */
+ array_map( array( $this, 'maybe_delete_file' ), $files_to_remove );
+ }
+ }
+
+ /**
+ * Delete the "optimization status" transient.
+ */
+ $this->delete_running_status();
+ }
+
+ /**
+ * Maybe resize (and backup) an image.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param string $attachment_path The file path.
+ * @return bool True on success. False on failure.
+ */
+ protected function maybe_resize( $attachment_path ) {
+ if ( ! $this->is_image() ) {
+ return false;
+ }
+
+ $do_resize = get_imagify_option( 'resize_larger' );
+ $resize_width = get_imagify_option( 'resize_larger_w' );
+ $attachment_size = $this->filesystem->get_image_size( $attachment_path );
+
+ if ( ! $do_resize || ! $attachment_size || $resize_width >= $attachment_size['width'] ) {
+ return false;
+ }
+
+ $resized_attachment_path = $this->resize( $attachment_path, $attachment_size, $resize_width );
+
+ if ( is_wp_error( $resized_attachment_path ) ) {
+ return false;
+ }
+
+ $backuped = imagify_backup_file( $attachment_path );
+
+ if ( is_wp_error( $backuped ) ) {
+ return false;
+ }
+
+ return $this->filesystem->move( $resized_attachment_path, $attachment_path, true );
+ }
+
+ /**
+ * Tell if the files must be deleted after being optimized or restored.
+ * It sets the 2 properties $this->use_s3_settings and $this->delete_files.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param array $metadata Attachment metadata. Provide, only if it comes from a 'wp_generate_attachment_metadata' or 'wp_update_attachment_metadata' hook.
+ * @return array Attachment metadata. If not provided as argument, new values are fetched.
+ */
+ protected function set_deletion_status( $metadata = false ) {
+ global $as3cf;
+
+ if ( $metadata ) {
+ /**
+ * Metadata is provided: we were in a 'wp_generate_attachment_metadata' or 'wp_update_attachment_metadata' hook.
+ * This means we'll follow AS3CF settings to know if the local files must be sent to S3 and/or deleted.
+ */
+ $this->use_s3_settings = true;
+ $this->delete_files = $as3cf && $as3cf->get_setting( 'remove-local-file' ) && $this->can_send_to_s3();
+
+ return $metadata;
+ }
+
+ /**
+ * Metadata is not provided: we were not in a 'wp_generate_attachment_metadata' or 'wp_update_attachment_metadata' hook.
+ * So, we fetch the current meta value.
+ * This also means we won't follow AS3CF settings to know if the local files must be sent to S3 and/or deleted.
+ * In that case we'll send the files to S3 if they already are there, and delete them if they is a 'filesize' entry in the metadata.
+ */
+ $metadata = wp_get_attachment_metadata( $this->id, true );
+ $this->use_s3_settings = false;
+ $this->delete_files = isset( $metadata['filesize'] ) && $this->can_send_to_s3();
+
+ return $metadata;
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** S3 UTILITIES ============================================================================ */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Tell if AS3CF is set up.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_s3_setup() {
+ global $as3cf;
+ static $is;
+
+ if ( ! isset( $is ) ) {
+ $is = $as3cf && $as3cf->is_plugin_setup();
+ }
+
+ return $is;
+ }
+
+ /**
+ * Tell if an attachment is stored on S3.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @return array|bool The S3 info on success. False if the attachment is not on S3.
+ */
+ public function get_s3_info() {
+ global $as3cf;
+
+ if ( ! $as3cf ) {
+ return false;
+ }
+
+ if ( method_exists( $as3cf, 'get_attachment_s3_info' ) ) {
+ return $as3cf->get_attachment_s3_info( $this->id );
+ }
+
+ return $as3cf->get_attachment_provider_info( $this->id );
+ }
+
+ /**
+ * Get a file from S3.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return string|bool The file path on success, false on failure.
+ */
+ protected function get_file_from_s3( $file_path ) {
+ global $as3cf;
+
+ if ( ! $this->is_extension_supported() ) {
+ return false;
+ }
+
+ if ( ! $this->is_s3_setup() ) {
+ return false;
+ }
+
+ $s3_object = $this->get_s3_info();
+
+ if ( ! $s3_object ) {
+ // The attachment is not on S3.
+ return false;
+ }
+
+ $directory = $this->filesystem->dir_path( $s3_object['key'] );
+ $directory = $this->filesystem->is_root( $directory ) ? '' : $directory;
+ $s3_object['key'] = $directory . $this->filesystem->file_name( $file_path );
+
+ // Retrieve file from S3.
+ if ( method_exists( $as3cf->plugin_compat, 'copy_s3_file_to_server' ) ) {
+ $as3cf->plugin_compat->copy_s3_file_to_server( $s3_object, $file_path );
+ } else {
+ $as3cf->plugin_compat->copy_provider_file_to_server( $s3_object, $file_path );
+ }
+
+ return $this->filesystem->exists( $file_path ) ? $file_path : false;
+ }
+
+ /**
+ * Maybe send the attachment to S3.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param array $metadata The attachment metadata.
+ * @param string $attachment_path The attachment path.
+ * @param bool $remove_local_files True to let AS3CF delete the local files (if set in the settings). We usually don't want that, we do it by ourselves.
+ * @return bool True on success. False otherwize.
+ */
+ protected function maybe_send_attachment_to_s3( $metadata = null, $attachment_path = null, $remove_local_files = false ) {
+ global $as3cf;
+
+ if ( ! $this->can_send_to_s3() ) {
+ return false;
+ }
+
+ $s3_object = $this->get_s3_info();
+
+ if ( ! $s3_object ) {
+ return false;
+ }
+
+ $full_file_path = $this->get_original_path();
+
+ if ( ! $full_file_path ) {
+ // This is bad.
+ return false;
+ }
+
+ if ( method_exists( $as3cf, 'upload_attachment_to_s3' ) ) {
+ $s3_data = $as3cf->upload_attachment_to_s3( $this->id, $metadata, $attachment_path, false, $remove_local_files );
+ } else {
+ $s3_data = $as3cf->upload_attachment( $this->id, $metadata, $attachment_path, false, $remove_local_files );
+ }
+
+ return ! is_wp_error( $s3_data );
+ }
+
+ /**
+ * Tell if an attachment can be sent to S3.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ protected function can_send_to_s3() {
+ global $as3cf;
+ static $can = array();
+ static $copy_to_s3;
+
+ if ( isset( $can[ $this->id ] ) ) {
+ return $can[ $this->id ];
+ }
+
+ if ( ! isset( $copy_to_s3 ) ) {
+ $copy_to_s3 = $as3cf && $as3cf->get_setting( 'copy-to-s3' );
+ }
+
+ $is_s3_setup = $this->is_s3_setup();
+ $s3_object = $this->get_s3_info();
+ // S3 is set up and the attachment is on S3.
+ $can[ $this->id ] = $is_s3_setup && $s3_object;
+
+ if ( $can[ $this->id ] && ! empty( $this->use_s3_settings ) ) {
+ // Use AS3CF setting to tell if we're allowed to send the files.
+ $can[ $this->id ] = $copy_to_s3;
+ }
+
+ /**
+ * Filter the result of Imagify_AS3CF_Attachment::can_send_to_s3().
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param bool $can True if the attachment can be sent. False otherwize.
+ * @param int $id The attachment ID.
+ * @param array $s3_object The S3 infos.
+ * @param bool $is_s3_setup AS3CF is set up or not.
+ * @param bool $copy_to_s3 AS3CF setting that tells if a "new" attachment can be sent.
+ * @param bool $use_s3_settings Tell if we must use AS3CF setting in this case.
+ */
+ $can[ $this->id ] = (bool) apply_filters( 'imagify_can_send_to_s3', $can[ $this->id ], $this->id, $s3_object, $is_s3_setup, $copy_to_s3, $this->use_s3_settings );
+
+ return $can[ $this->id ];
+ }
+
+ /**
+ * Maybe delete the local file.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return bool True if deleted or doesn't exist. False on failure or if the file is not supposed to be deleted.
+ */
+ protected function maybe_delete_file( $file_path ) {
+ if ( ! $this->file_should_be_deleted( $file_path ) ) {
+ return false;
+ }
+
+ if ( ! $this->filesystem->exists( $file_path ) ) {
+ return true;
+ }
+
+ return $this->filesystem->delete( $file_path, false, 'f' );
+ }
+
+ /**
+ * Tell if a file should be deleted.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param string $file_path The file path.
+ * @return bool True to delete, false to keep.
+ */
+ protected function file_should_be_deleted( $file_path ) {
+ if ( ! $file_path || ! $this->delete_files ) {
+ // We keep the file.
+ return false;
+ }
+
+ /** This hook is documented in /amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php. */
+ $preserve = apply_filters( 'as3cf_preserve_file_from_local_removal', false, $file_path );
+
+ return false === $preserve;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-as3cf-deprecated.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-as3cf-deprecated.php
new file mode 100644
index 00000000..ed813426
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-as3cf-deprecated.php
@@ -0,0 +1,313 @@
+is_plugin_setup() ) {
+ return;
+ }
+
+ // Remove from the list files that exist.
+ $ids = array_flip( $ids );
+
+ foreach ( $ids as $id => $i ) {
+ if ( empty( $results['filenames'][ $id ] ) ) {
+ // Problem.
+ unset( $ids[ $id ] );
+ continue;
+ }
+
+ $file_path = get_imagify_attached_file( $results['filenames'][ $id ] );
+
+ /** This filter is documented in inc/deprecated/deprecated.php. */
+ $file_path = apply_filters( 'imagify_file_path', $file_path, $id, 'as3cf_maybe_copy_files_from_s3' );
+
+ if ( ! $file_path || $this->filesystem->exists( $file_path ) ) {
+ // The file exists, no need to retrieve it from S3.
+ unset( $ids[ $id ] );
+ } else {
+ $ids[ $id ] = $file_path;
+ }
+ }
+
+ if ( ! $ids ) {
+ // All files are already on the server.
+ return;
+ }
+
+ // Determine which files are on S3.
+ $ids = array_flip( $ids );
+ $sql_ids = implode( ',', $ids );
+
+ $s3_data = $wpdb->get_results( // WPCS: unprepared SQL ok.
+ "SELECT pm.post_id as id, pm.meta_value as value
+ FROM $wpdb->postmeta as pm
+ WHERE pm.meta_key = 'amazonS3_info'
+ AND pm.post_id IN ( $sql_ids )
+ ORDER BY pm.post_id DESC",
+ ARRAY_A
+ );
+
+ $wpdb->flush();
+
+ if ( ! $s3_data ) {
+ return;
+ }
+
+ unset( $sql_ids );
+ $s3_data = Imagify_DB::combine_query_results( $ids, $s3_data, true );
+
+ // Retrieve the missing files from S3.
+ $ids = array_flip( $ids );
+
+ foreach ( $s3_data as $id => $s3_object ) {
+ $s3_object = maybe_unserialize( $s3_object );
+ $file_path = $ids[ $id ];
+
+ $attachment_backup_path = get_imagify_attachment_backup_path( $file_path );
+ $attachment_status = isset( $results['statuses'][ $id ] ) ? $results['statuses'][ $id ] : false;
+ $attachment_optimization_level = isset( $results['optimization_levels'][ $id ] ) ? $results['optimization_levels'][ $id ] : false;
+
+ // Don't try to re-optimize if there is no backup file.
+ if ( 'success' === $attachment_status && $optimization_level !== $attachment_optimization_level && ! $this->filesystem->exists( $attachment_backup_path ) ) {
+ unset( $s3_data[ $id ], $ids[ $id ] );
+ continue;
+ }
+
+ $directory = $this->filesystem->dir_path( $s3_object['key'] );
+ $directory = $this->filesystem->is_root( $directory ) ? '' : $directory;
+ $s3_object['key'] = $directory . $this->filesystem->file_name( $file_path );
+
+ // Retrieve file from S3.
+ if ( method_exists( $as3cf->plugin_compat, 'copy_s3_file_to_server' ) ) {
+ $as3cf->plugin_compat->copy_s3_file_to_server( $s3_object, $file_path );
+ } else {
+ $as3cf->plugin_compat->copy_provider_file_to_server( $s3_object, $file_path );
+ }
+
+ unset( $s3_data[ $id ], $ids[ $id ] );
+ }
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** AUTOMATIC OPTIMIZATION: OPTIMIZE AFTER S3 HAS DONE ITS WORK ============================= */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Filter the generated attachment meta data.
+ * This is used when a new attachment has just been uploaded (or not, when wp_generate_attachment_metadata() is used).
+ * We use it to tell the difference later in wp_update_attachment_metadata().
+ *
+ * @since 1.6.6
+ * @since 1.8.4 Deprecated
+ * @author Grégory Viguier
+ * @see $this->do_async_job()
+ * @deprecated
+ *
+ * @param array $metadata An array of attachment meta data.
+ * @param int $attachment_id Current attachment ID.
+ * @return array
+ */
+ public function store_upload_ids( $metadata, $attachment_id ) {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.8.4' );
+
+ if ( imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
+ $this->uploads[ $attachment_id ] = 1;
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * After an image (maybe) being sent to S3, launch an async optimization.
+ *
+ * @since 1.6.6
+ * @since 1.8.4 Deprecated
+ * @author Grégory Viguier
+ * @see $this->store_upload_ids()
+ * @deprecated
+ *
+ * @param array $metadata An array of attachment meta data.
+ * @param int $attachment_id Current attachment ID.
+ * @return array
+ */
+ public function do_async_job( $metadata, $attachment_id ) {
+ static $auto_optimize;
+
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.8.4' );
+
+ $is_new_upload = ! empty( $this->uploads[ $attachment_id ] );
+ unset( $this->uploads[ $attachment_id ] );
+
+ if ( ! $metadata || ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
+ return $metadata;
+ }
+
+ if ( ! isset( $auto_optimize ) ) {
+ $auto_optimize = Imagify_Requirements::is_api_key_valid() && get_imagify_option( 'auto_optimize' );
+ }
+
+ if ( $is_new_upload ) {
+ // It's a new upload.
+ if ( ! $auto_optimize ) {
+ // Auto-optimization is disabled.
+ return $metadata;
+ }
+
+ /** This filter is documented in inc/common/attachments.php. */
+ $optimize = apply_filters( 'imagify_auto_optimize_attachment', true, $attachment_id, $metadata );
+
+ if ( ! $optimize ) {
+ return $metadata;
+ }
+ }
+
+ if ( ! $is_new_upload ) {
+ $attachment = get_imagify_attachment( self::CONTEXT, $attachment_id, 'as3cf_async_job' );
+
+ if ( ! $attachment->get_data() ) {
+ // It's not a new upload and the attachment is not optimized yet.
+ return $metadata;
+ }
+ }
+
+ $data = array();
+
+ // Some specifics for the image editor.
+ if ( isset( $_POST['action'], $_POST['do'], $_POST['postid'] ) && 'image-editor' === $_POST['action'] && (int) $_POST['postid'] === $attachment_id ) { // WPCS: CSRF ok.
+ check_ajax_referer( 'image_editor-' . $_POST['postid'] );
+ $data = $_POST;
+ }
+
+ imagify_do_async_job( array(
+ 'action' => 'imagify_async_optimize_as3cf',
+ '_ajax_nonce' => wp_create_nonce( 'imagify_async_optimize_as3cf' ),
+ 'post_id' => $attachment_id,
+ 'metadata' => $metadata,
+ 'data' => $data,
+ ) );
+
+ return $metadata;
+ }
+
+ /**
+ * Once an image has been sent to S3, optimize it and send it again.
+ *
+ * @since 1.6.6
+ * @since 1.8.4 Deprecated
+ * @author Grégory Viguier
+ * @deprecated
+ */
+ public function optimize() {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.8.4' );
+
+ check_ajax_referer( 'imagify_async_optimize_as3cf' );
+
+ if ( empty( $_POST['post_id'] ) || ! imagify_current_user_can( 'auto-optimize' ) ) {
+ die();
+ }
+
+ $attachment_id = absint( $_POST['post_id'] );
+
+ if ( ! $attachment_id || empty( $_POST['metadata'] ) || ! is_array( $_POST['metadata'] ) || empty( $_POST['metadata']['sizes'] ) ) {
+ die();
+ }
+
+ if ( ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
+ die();
+ }
+
+ $optimization_level = null;
+ $attachment = get_imagify_attachment( self::CONTEXT, $attachment_id, 'as3cf_optimize' );
+
+ // Some specifics for the image editor.
+ if ( ! empty( $_POST['data']['do'] ) ) {
+ $optimization_level = $attachment->get_optimization_level();
+
+ // Remove old optimization data.
+ $attachment->delete_imagify_data();
+
+ if ( 'restore' === $_POST['data']['do'] ) {
+ // Restore the backup file.
+ $attachment->restore();
+ }
+ }
+
+ // Optimize it.
+ $attachment->optimize( $optimization_level, $_POST['metadata'] );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-assets-deprecated.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-assets-deprecated.php
new file mode 100644
index 00000000..ed1e5670
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-assets-deprecated.php
@@ -0,0 +1,104 @@
+is_intercom ) || empty( $user->display_support ) ) {
+ return;
+ }
+ ?>
+
+ scripts[ $handle ] ) ) {
+ // If we registered it, it's one of our scripts.
+ $handle = self::JS_PREFIX . $handle;
+ }
+
+ $wp_scripts = wp_scripts();
+ $dependencies = $wp_scripts->query( $handle );
+
+ if ( ! $dependencies || ! $dependencies->deps ) {
+ return;
+ }
+
+ $dependencies = array_flip( $dependencies->deps );
+
+ if ( ! isset( $dependencies['heartbeat'] ) ) {
+ return;
+ }
+
+ $suffix = SCRIPT_DEBUG ? '' : '.min';
+ $depts = [ 'jquery' ];
+
+ if ( version_compare( $wp_version, '5.0.0' ) >= 0 ) {
+ $depts[] = 'wp-hooks';
+ }
+
+ wp_register_script( 'heartbeat', "/wp-includes/js/heartbeat$suffix.js", $depts, false, true ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion
+
+ if ( $wp_scripts->get_data( 'heartbeat', 'data' ) ) {
+ return;
+ }
+
+ /** This filter is documented in /wp-includes/script-loader.php */
+ $data = apply_filters( 'heartbeat_settings', [] );
+
+ if ( empty( $data['nonce'] ) ) {
+ $data = wp_heartbeat_settings( $data );
+ }
+
+ wp_localize_script( 'heartbeat', 'heartbeatSettings', $data );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-attachment.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-attachment.php
new file mode 100644
index 00000000..34315bad
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-attachment.php
@@ -0,0 +1,850 @@
+get_original_path() );
+ }
+
+ /**
+ * Get the attachment optimization data.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return array
+ */
+ public function get_data() {
+ $data = get_post_meta( $this->id, '_imagify_data', true );
+ return is_array( $data ) ? $data : array();
+ }
+
+ /**
+ * Get the attachment optimization level.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return int
+ */
+ public function get_optimization_level() {
+ $level = get_post_meta( $this->id, '_imagify_optimization_level', true );
+ return false !== $level ? (int) $level : false;
+ }
+
+ /**
+ * Get the attachment optimization status (success or error).
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return string
+ */
+ public function get_status() {
+ $status = get_post_meta( $this->id, '_imagify_status', true );
+ return is_string( $status ) ? $status : '';
+ }
+
+ /**
+ * Get the original attachment path.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return string
+ */
+ public function get_original_path() {
+ return get_attached_file( $this->id );
+ }
+
+ /**
+ * Get the original attachment URL.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return string
+ */
+ public function get_original_url() {
+ return wp_get_attachment_url( $this->id );
+ }
+
+ /**
+ * Get width and height of the original image.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array
+ */
+ public function get_dimensions() {
+ if ( ! $this->is_image() ) {
+ return parent::get_dimensions();
+ }
+
+ $values = wp_get_attachment_image_src( $this->id, 'full' );
+
+ return array(
+ 'width' => $values[1],
+ 'height' => $values[2],
+ );
+ }
+
+ /**
+ * Update the metadata size of the attachment.
+ *
+ * @since 1.2
+ * @access public
+ *
+ * @return bool
+ */
+ public function update_metadata_size() {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() || ! $this->is_image() ) {
+ return false;
+ }
+
+ $size = $this->filesystem->get_image_size( $this->get_original_path() );
+
+ if ( ! $size ) {
+ return false;
+ }
+
+ /**
+ * Triggered before updating an image width and height into its metadata.
+ *
+ * @since 1.8.4
+ * @see Imagify_Filesystem->get_image_size()
+ * @author Grégory Viguier
+ *
+ * @param int $attachment_id The attachment ID.
+ * @param array $size {
+ * An array with, among other data:
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ */
+ do_action( 'before_imagify_update_metadata_size', $this->id, $size );
+
+ $metadata = wp_get_attachment_metadata( $this->id );
+ $metadata['width'] = $size['width'];
+ $metadata['height'] = $size['height'];
+
+ wp_update_attachment_metadata( $this->id, $metadata );
+
+ /**
+ * Triggered after updating an image width and height into its metadata.
+ *
+ * @since 1.8.4
+ * @see Imagify_Filesystem->get_image_size()
+ * @author Grégory Viguier
+ *
+ * @param int $attachment_id The attachment ID.
+ * @param array $size {
+ * An array with, among other data:
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * }
+ */
+ do_action( 'after_imagify_update_metadata_size', $this->id, $size );
+
+ return true;
+ }
+
+ /**
+ * Fills statistics data with values from $data array.
+ *
+ * @since 1.0
+ * @since 1.6.5 Not static anymore.
+ * @since 1.6.6 Removed the attachment ID parameter.
+ * @since 1.7 Removed the image URL parameter.
+ * @access public
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param string $size The attachment size key.
+ * @return bool|array False if the original size has an error or an array contains the data for other result.
+ */
+ public function fill_data( $data, $response, $size = 'full' ) {
+ $data = is_array( $data ) ? $data : array();
+ $data['sizes'] = ! empty( $data['sizes'] ) && is_array( $data['sizes'] ) ? $data['sizes'] : array();
+
+ if ( empty( $data['stats'] ) ) {
+ $data['stats'] = array(
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ $error = $response->get_error_message();
+ $error_status = 'error';
+
+ $data['sizes'][ $size ] = array(
+ 'success' => false,
+ 'error' => $error,
+ );
+
+ // Update the error status for the original size.
+ if ( 'full' === $size ) {
+ update_post_meta( $this->id, '_imagify_data', $data );
+
+ if ( false !== strpos( $error, 'This image is already compressed' ) ) {
+ $error_status = 'already_optimized';
+ }
+
+ update_post_meta( $this->id, '_imagify_status', $error_status );
+
+ return false;
+ }
+ } else {
+ $response = (object) array_merge( array(
+ 'original_size' => 0,
+ 'new_size' => 0,
+ 'percent' => 0,
+ ), (array) $response );
+
+ $data['sizes'][ $size ] = array(
+ 'success' => true,
+ 'original_size' => $response->original_size,
+ 'optimized_size' => $response->new_size,
+ 'percent' => $response->percent,
+ );
+
+ $data['stats']['original_size'] += $response->original_size;
+ $data['stats']['optimized_size'] += $response->new_size;
+ } // End if().
+
+ return $data;
+ }
+
+ /**
+ * Create a thumbnail if it doesn't exist.
+ *
+ * @since 1.6.10
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $thumbnail_data The thumbnail data (width, height, crop, name, file).
+ * @return bool|array|object True if the file exists. An array of thumbnail data if the file has just been created (width, height, crop, file). A WP_Error object on error.
+ */
+ protected function create_thumbnail( $thumbnail_data ) {
+ $thumbnail_size = $thumbnail_data['name'];
+ $metadata = wp_get_attachment_metadata( $this->id );
+ $metadata_sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array();
+
+ $original_dirname = $this->filesystem->dir_path( $this->get_original_path() );
+ $thumbnail_path = $original_dirname . $thumbnail_data['file'];
+
+ if ( ! empty( $metadata_sizes[ $thumbnail_size ] ) && $this->filesystem->exists( $thumbnail_path ) ) {
+ $this->filesystem->chmod_file( $thumbnail_path );
+ return true;
+ }
+
+ // Get the editor.
+ $editor = $this->get_editor( $this->get_backup_path() );
+
+ if ( is_wp_error( $editor ) ) {
+ return $editor;
+ }
+
+ // Create the file.
+ $result = $editor->multi_resize( array( $thumbnail_size => $thumbnail_data ) );
+
+ if ( ! $result ) {
+ return new WP_Error( 'image_resize_error' );
+ }
+
+ // The file name can change from what we expected (1px wider, etc).
+ $backup_dirname = $this->filesystem->dir_path( $this->get_backup_path() );
+ $backup_thumb_path = $backup_dirname . $result[ $thumbnail_size ]['file'];
+ $thumbnail_path = $original_dirname . $result[ $thumbnail_size ]['file'];
+
+ // Since we used the backup image as source, the new image is still in the backup folder, we need to move it.
+ $moved = $this->filesystem->move( $backup_thumb_path, $thumbnail_path, true );
+
+ if ( ! $moved ) {
+ return new WP_Error( 'image_resize_error' );
+ }
+
+ return reset( $result );
+ }
+
+ /**
+ * Create all missing thumbnails if they don't exist and update the attachment metadata.
+ *
+ * @since 1.6.10
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @param array $missing_sizes An array of thumbnail data (width, height, crop, name, file) for each thumbnail size.
+ * @return array An array of thumbnail data (width, height, crop, file).
+ */
+ protected function create_missing_thumbnails( $missing_sizes ) {
+ if ( ! $missing_sizes || ! $this->is_image() ) {
+ return array();
+ }
+
+ $metadata = wp_get_attachment_metadata( $this->id );
+ $metadata['sizes'] = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array();
+ $thumbnail_new_datas = array();
+ $thumbnail_metadatas = array();
+
+ // Create the missing thumbnails.
+ foreach ( $missing_sizes as $size_name => $thumbnail_data ) {
+ $result = $this->create_thumbnail( $thumbnail_data );
+
+ if ( is_array( $result ) ) {
+ // New file.
+ $thumbnail_new_datas[ $size_name ] = $result;
+ unset( $thumbnail_new_datas[ $size_name ]['name'] );
+ } elseif ( true === $result ) {
+ // The file already exists.
+ $thumbnail_metadatas[ $size_name ] = $metadata['sizes'][ $size_name ];
+ }
+ }
+
+ // Save the new data into the attachment metadata.
+ if ( $thumbnail_new_datas ) {
+ $metadata['sizes'] = array_merge( $metadata['sizes'], $thumbnail_new_datas );
+
+ /**
+ * Here we don't use wp_update_attachment_metadata() to prevent triggering unwanted hooks.
+ */
+ update_post_meta( $this->id, '_wp_attachment_metadata', $metadata );
+ }
+
+ return array_merge( $thumbnail_metadatas, $thumbnail_new_datas );
+ }
+
+ /**
+ * Optimize all sizes with Imagify.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @param array $metadata The attachment meta data.
+ * @return array $optimized_data The optimization data.
+ */
+ public function optimize( $optimization_level = null, $metadata = array() ) {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return;
+ }
+
+ $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
+ $metadata = $metadata ? $metadata : wp_get_attachment_metadata( $this->id );
+ $sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) && $this->is_image() ? $metadata['sizes'] : array();
+
+ // To avoid issue with "original_size" at 0 in "_imagify_data".
+ if ( 0 === (int) $this->get_stats_data( 'original_size' ) ) {
+ $this->delete_imagify_data();
+ }
+
+ // Check if the full size is already optimized.
+ if ( $this->is_optimized() && $this->get_optimization_level() === $optimization_level ) {
+ return;
+ }
+
+ // Get file path & URL for original image.
+ $attachment_path = $this->get_original_path();
+ $attachment_url = $this->get_original_url();
+ $attachment_original_size = $this->get_original_size( false );
+
+ /**
+ * Fires before optimizing an attachment.
+ *
+ * @since 1.0
+ *
+ * @param int $id The attachment ID.
+ */
+ do_action( 'before_imagify_optimize_attachment', $this->id );
+
+ $this->set_running_status();
+
+ // Get the resize values for the original size.
+ $resized = false;
+ $do_resize = $this->is_image() && get_imagify_option( 'resize_larger' );
+
+ if ( $do_resize ) {
+ $resize_width = get_imagify_option( 'resize_larger_w' );
+ $attachment_size = $this->filesystem->get_image_size( $attachment_path );
+
+ if ( $attachment_size && $resize_width < $attachment_size['width'] ) {
+ $resized_attachment_path = $this->resize( $attachment_path, $attachment_size, $resize_width );
+
+ if ( ! is_wp_error( $resized_attachment_path ) ) {
+ // TODO (@Greg): Send an error message if the backup fails.
+ imagify_backup_file( $attachment_path );
+
+ $this->filesystem->move( $resized_attachment_path, $attachment_path, true );
+
+ $resized = true;
+ }
+ }
+ }
+
+ // Optimize the original size.
+ $response = do_imagify( $attachment_path, array(
+ 'optimization_level' => $optimization_level,
+ 'context' => $this->get_context(),
+ 'resized' => $resized,
+ 'original_size' => $attachment_original_size,
+ ) );
+
+ $data = $this->fill_data( null, $response );
+
+ /**
+ * Filter the optimization data of the full size.
+ *
+ * @since 1.8
+ * @author Grégory Viguier
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param int $id The attachment ID.
+ * @param string $attachment_path The attachment path.
+ * @param string $attachment_url The attachment URL.
+ * @param string $size_key The attachment size key. The value is obviously 'full' but it's kept for concistancy with other filters.
+ * @param int $optimization_level The optimization level.
+ * @param array $metadata WP metadata.
+ */
+ $data = apply_filters( 'imagify_fill_full_size_data', $data, $response, $this->id, $attachment_path, $attachment_url, 'full', $optimization_level, $metadata );
+
+ // Save the optimization level.
+ update_post_meta( $this->id, '_imagify_optimization_level', $optimization_level );
+
+ // If we resized the original with success, we have to update the attachment metadata.
+ // If not, WordPress keeps the old attachment size.
+ if ( $resized ) {
+ $this->update_metadata_size();
+ }
+
+ if ( ! $data ) {
+ $this->delete_running_status();
+ return;
+ }
+
+ // Optimize all thumbnails.
+ if ( $sizes ) {
+ $disallowed_sizes = get_imagify_option( 'disallowed-sizes' );
+ $is_active_for_network = imagify_is_active_for_network();
+ $attachment_path_dirname = $this->filesystem->dir_path( $attachment_path );
+ $attachment_url_dirname = $this->filesystem->dir_path( $attachment_url );
+
+ foreach ( $sizes as $size_key => $size_data ) {
+ $thumbnail_path = $attachment_path_dirname . $size_data['file'];
+ $thumbnail_url = $attachment_url_dirname . $size_data['file'];
+
+ // Check if this size has to be optimized.
+ if ( ! $is_active_for_network && isset( $disallowed_sizes[ $size_key ] ) ) {
+ $data['sizes'][ $size_key ] = array(
+ 'success' => false,
+ 'error' => __( 'This size is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
+ );
+
+ /**
+ * Filter the optimization data of an unauthorized thumbnail.
+ *
+ * @since 1.8
+ * @author Grégory Viguier
+ *
+ * @param array $data The statistics data.
+ * @param int $id The attachment ID.
+ * @param string $thumbnail_path The thumbnail path.
+ * @param string $thumbnail_url The thumbnail URL.
+ * @param string $size_key The thumbnail size key.
+ * @param int $optimization_level The optimization level.
+ * @param array $metadata WP metadata.
+ */
+ $data = apply_filters( 'imagify_fill_unauthorized_thumbnail_data', $data, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata );
+ continue;
+ }
+
+ // Optimize the thumbnail size.
+ $response = do_imagify( $thumbnail_path, array(
+ 'backup' => false,
+ 'optimization_level' => $optimization_level,
+ 'context' => $this->get_context(),
+ ) );
+
+ $data = $this->fill_data( $data, $response, $size_key );
+
+ /**
+ * Filter the optimization data of a specific thumbnail.
+ *
+ * @since 1.0
+ * @since 1.8 Added $metadata.
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param int $id The attachment ID.
+ * @param string $thumbnail_path The thumbnail path.
+ * @param string $thumbnail_url The thumbnail URL.
+ * @param string $size_key The thumbnail size key.
+ * @param int $optimization_level The optimization level.
+ * @param array $metadata WP metadata.
+ */
+ $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level, $metadata );
+ } // End foreach().
+ } // End if().
+
+ $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 );
+
+ update_post_meta( $this->id, '_imagify_data', $data );
+ update_post_meta( $this->id, '_imagify_status', 'success' );
+
+ $optimized_data = $this->get_data();
+
+ /**
+ * Fires after optimizing an attachment.
+ *
+ * @since 1.0
+ *
+ * @param int $id The attachment ID.
+ * @param array $optimized_data The optimization data.
+ */
+ do_action( 'after_imagify_optimize_attachment', $this->id, $optimized_data );
+
+ $this->delete_running_status();
+
+ return $optimized_data;
+ }
+
+ /**
+ * Optimize missing thumbnail sizes with Imagify.
+ *
+ * @since 1.6.10
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @return array|object An array of thumbnail data, size by size. A WP_Error object on failure.
+ */
+ public function optimize_missing_thumbnails( $optimization_level = null ) {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() || ! $this->is_image() ) {
+ return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
+ }
+
+ $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
+ $missing_sizes = $this->get_unoptimized_sizes();
+
+ if ( ! $missing_sizes ) {
+ // We have everything we need.
+ return array();
+ }
+
+ // Stop the process if there is no backup file to use.
+ if ( ! $this->has_backup() ) {
+ return new WP_Error( 'no_backup', __( 'This file has no backup file.', 'imagify' ) );
+ }
+
+ /**
+ * Fires before optimizing the missing thumbnails.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ * @see $this->get_unoptimized_sizes()
+ *
+ * @param int $id The attachment ID.
+ * @param array $missing_sizes An array of the missing sizes.
+ */
+ do_action( 'before_imagify_optimize_missing_thumbnails', $this->id, $missing_sizes );
+
+ $this->set_running_status();
+
+ $errors = new WP_Error();
+
+ // Create the missing thumbnails.
+ $result_sizes = $this->create_missing_thumbnails( $missing_sizes );
+ $failed_sizes = array_diff_key( $missing_sizes, $result_sizes );
+
+ if ( $failed_sizes ) {
+ $failed_count = count( $failed_sizes );
+ /* translators: %d is a number of thumbnails. */
+ $error_message = _n( '%d thumbnail failed to be created', '%d thumbnails failed to be created', $failed_count, 'imagify' );
+ $error_message = sprintf( $error_message, $failed_count );
+
+ $errors->add( 'image_resize_error', $error_message, array(
+ 'nbr_failed' => $failed_count,
+ 'sizes_failed' => $failed_sizes,
+ 'sizes_succeeded' => $result_sizes,
+ ) );
+ }
+
+ if ( ! $result_sizes ) {
+ $this->delete_running_status();
+ return $errors;
+ }
+
+ // Optimize.
+ $imagify_data = $this->get_data();
+ $original_dirname = $this->filesystem->dir_path( $this->get_original_path() );
+ $orig_url_dirname = $this->filesystem->dir_path( $this->get_original_url() );
+
+ foreach ( $result_sizes as $size_name => $thumbnail_data ) {
+ $thumbnail_path = $original_dirname . $thumbnail_data['file'];
+ $thumbnail_url = $orig_url_dirname . $thumbnail_data['file'];
+
+ // Optimize the thumbnail size.
+ $response = do_imagify( $thumbnail_path, array(
+ 'backup' => false,
+ 'optimization_level' => $optimization_level,
+ 'context' => $this->get_context(),
+ ) );
+
+ $imagify_data = $this->fill_data( $imagify_data, $response, $size_name );
+ $metadata = wp_get_attachment_metadata( $this->id );
+
+ /** This filter is documented in inc/classes/class-imagify-attachment.php. */
+ $imagify_data = apply_filters( 'imagify_fill_thumbnail_data', $imagify_data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_name, $optimization_level, $metadata );
+ }
+
+ // Save Imagify data.
+ $imagify_data['stats']['percent'] = round( ( ( $imagify_data['stats']['original_size'] - $imagify_data['stats']['optimized_size'] ) / $imagify_data['stats']['original_size'] ) * 100, 2 );
+
+ update_post_meta( $this->id, '_imagify_data', $imagify_data );
+
+ /**
+ * Fires after optimizing the missing thumbnails.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ * @see $this->create_missing_thumbnails()
+ *
+ * @param int $id The attachment ID.
+ * @param array $result_sizes An array of created thumbnails.
+ * @param object $errors A WP_Error object that stores thumbnail creation failures.
+ */
+ do_action( 'after_imagify_optimize_missing_thumbnails', $this->id, $result_sizes, $errors );
+
+ $this->delete_running_status();
+
+ // Return the result.
+ if ( $errors->get_error_codes() ) {
+ return $errors;
+ }
+
+ return $result_sizes;
+ }
+
+ /**
+ * Re-optimize the given thumbnail sizes to the same level.
+ * Before doing this, the given sizes must be restored.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes The sizes to optimize.
+ * @return array|void A WP_Error object on failure.
+ */
+ public function reoptimize_thumbnails( $sizes ) {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() || ! $this->is_image() ) {
+ return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
+ }
+
+ if ( ! $sizes || ! is_array( $sizes ) ) {
+ return;
+ }
+
+ /**
+ * Fires before re-optimizing some thumbnails of an attachment.
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ *
+ * @param int $id The attachment ID.
+ * @param array $sizes The sizes to optimize.
+ */
+ do_action( 'before_imagify_reoptimize_attachment_thumbnails', $this->id, $sizes );
+
+ $this->set_running_status();
+
+ $data = $this->get_data();
+
+ $data['sizes'] = ! empty( $data['sizes'] ) && is_array( $data['sizes'] ) ? $data['sizes'] : array();
+
+ foreach ( $sizes as $size_key => $size_data ) {
+ // In case it's a disallowed size, fill in the new data. If it's not, it will be overwritten by $this->fill_data() later.
+ $data['sizes'][ $size_key ] = array(
+ 'success' => false,
+ 'error' => __( 'This size is not authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
+ );
+ }
+
+ // Update global attachment stats.
+ $data['stats'] = array(
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ );
+
+ foreach ( $data['sizes'] as $size_data ) {
+ if ( ! empty( $size_data['original_size'] ) ) {
+ $data['stats']['original_size'] += $size_data['original_size'];
+ }
+ if ( ! empty( $size_data['optimized_size'] ) ) {
+ $data['stats']['optimized_size'] += $size_data['optimized_size'];
+ }
+ }
+
+ // Remove disallowed sizes.
+ if ( ! imagify_is_active_for_network() ) {
+ $sizes = array_diff_key( $sizes, get_imagify_option( 'disallowed-sizes' ) );
+ }
+
+ if ( ! $sizes ) {
+ $data['stats']['percent'] = $data['stats']['original_size'] ? round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 ) : 0;
+ update_post_meta( $this->id, '_imagify_data', $data );
+ $this->delete_running_status();
+ return;
+ }
+
+ $optimization_level = $this->get_optimization_level();
+ $thumbnail_path = $this->get_original_path();
+ $thumbnail_url = $this->get_original_url();
+ $attachment_path_dirname = $this->filesystem->dir_path( $thumbnail_path );
+ $attachment_url_dirname = $this->filesystem->dir_path( $thumbnail_url );
+
+ foreach ( $sizes as $size_key => $size_data ) {
+ $thumbnail_path = $attachment_path_dirname . $size_data['file'];
+ $thumbnail_url = $attachment_url_dirname . $size_data['file'];
+
+ // Optimize the thumbnail size.
+ $response = do_imagify( $thumbnail_path, array(
+ 'backup' => false,
+ 'optimization_level' => $optimization_level,
+ 'context' => $this->get_context(),
+ ) );
+
+ $data = $this->fill_data( $data, $response, $size_key );
+
+ /** This filter is documented in /inc/classes/class-imagify-attachment.php. */
+ $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level );
+ } // End foreach().
+
+ $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 );
+
+ update_post_meta( $this->id, '_imagify_data', $data );
+
+ /**
+ * Fires after re-optimizing some thumbnails of an attachment.
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ *
+ * @param int $id The attachment ID.
+ * @param array $sizes The sizes to optimize.
+ */
+ do_action( 'after_imagify_reoptimize_attachment_thumbnails', $this->id, $sizes );
+
+ $this->delete_running_status();
+ }
+
+ /**
+ * Process an attachment restoration from the backup file.
+ *
+ * @since 1.0
+ * @access public
+ *
+ * @return void
+ */
+ public function restore() {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return;
+ }
+
+ // Stop the process if there is no backup file to restore.
+ if ( ! $this->has_backup() ) {
+ return;
+ }
+
+ $backup_path = $this->get_backup_path();
+ $attachment_path = $this->get_original_path();
+
+ /**
+ * Fires before restoring an attachment.
+ *
+ * @since 1.0
+ *
+ * @param int $id The attachment ID
+ */
+ do_action( 'before_imagify_restore_attachment', $this->id );
+
+ // Create the original image from the backup.
+ $this->filesystem->copy( $backup_path, $attachment_path, true );
+ $this->filesystem->chmod_file( $attachment_path );
+
+ if ( $this->is_image() ) {
+ if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/image.php';
+ }
+
+ wp_generate_attachment_metadata( $this->id, $attachment_path );
+
+ // Restore the original size in the metadata.
+ $this->update_metadata_size();
+ }
+
+ // Remove old optimization data.
+ $this->delete_imagify_data();
+
+ /**
+ * Fires after restoring an attachment.
+ *
+ * @since 1.0
+ *
+ * @param int $id The attachment ID
+ */
+ do_action( 'after_imagify_restore_attachment', $this->id );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php
new file mode 100644
index 00000000..cb9260d0
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php
@@ -0,0 +1,55 @@
+prevent_auto_optimization_when_generating_thumbnails()
+ *
+ * @param array $metadata An array of attachment meta data.
+ * @param int $attachment_id Current attachment ID.
+ * @param string $context Additional context. Can be 'create' when metadata was initially created for new attachment or 'update' when the metadata was updated.
+ * @return array An array of attachment meta data.
+ */
+ public function allow_auto_optimization_when_generating_thumbnails( $metadata, $attachment_id, $context = null ) {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9.10' );
+
+ if ( ! empty( $context ) && 'create' !== $context ) {
+ return $metadata;
+ }
+
+ // Fired from wp_generate_attachment_metadata(): $context is empty (WP < 5.3) or equal to 'create' (>P >= 5.3).
+ static::allow_optimization_internally( $attachment_id );
+ return $metadata;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php
new file mode 100644
index 00000000..9c20b42d
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php
@@ -0,0 +1,181 @@
+store_old_backup_path()
+ * @deprecated
+ *
+ * @param string $return_url The URL the user will be redirected to.
+ * @return string The same URL.
+ */
+ public function optimize( $return_url ) {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.8.4' );
+
+ $attachment = $this->get_attachment();
+
+ if ( $attachment->get_data() ) {
+ /**
+ * The old images have been optimized in the past.
+ */
+ // Use the same otimization level for the new ones.
+ $optimization_level = $attachment->get_optimization_level();
+
+ // Remove old optimization data.
+ $attachment->delete_imagify_data();
+
+ // Optimize and overwrite the previous backup file if exists and needed.
+ add_filter( 'imagify_backup_overwrite_backup', '__return_true', 42 );
+ $attachment->optimize( $optimization_level );
+ remove_filter( 'imagify_backup_overwrite_backup', '__return_true', 42 );
+ }
+
+ $filesystem = Imagify_Filesystem::get_instance();
+
+ /**
+ * Delete the old backup file.
+ */
+ if ( ! $this->old_backup_path || ! $filesystem->exists( $this->old_backup_path ) ) {
+ // The user didn't choose to rename the files, or there is no old backup.
+ $this->old_backup_path = null;
+ return $return_url;
+ }
+
+ $new_backup_path = $attachment->get_raw_backup_path();
+
+ if ( $new_backup_path === $this->old_backup_path ) {
+ // We don't want to delete the new backup.
+ $this->old_backup_path = null;
+ return $return_url;
+ }
+
+ // Finally, delete the old backup file.
+ $filesystem->delete( $this->old_backup_path );
+
+ $this->old_backup_path = null;
+ return $return_url;
+ }
+
+ /**
+ * When the user chooses to change the file name, store the old backup file path. This path will be used later to delete the file.
+ *
+ * @since 1.6.9
+ * @since 1.9.10 Deprecated.
+ * @deprecated
+ *
+ * @param string $new_filename The new file name.
+ * @param string $current_path The current file path.
+ * @param int $post_id The attachment ID.
+ * @return string The same file name.
+ */
+ public function store_old_backup_path( $new_filename, $current_path, $post_id ) {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9.10' );
+
+ if ( ! $this->media_id || $post_id !== $this->media_id ) {
+ return $new_filename;
+ }
+
+ $this->get_process();
+
+ if ( ! $this->process ) {
+ $this->media_id = 0;
+ return $new_filename;
+ }
+
+ $media = $this->process->get_media();
+ $backup_path = $media->get_backup_path();
+
+ if ( $backup_path ) {
+ $this->old_backup_path = $backup_path;
+
+ // Keep track of existing webp files.
+ $media_files = $media->get_media_files();
+
+ if ( $media_files ) {
+ foreach ( $media_files as $media_file ) {
+ $this->old_webp_paths[] = imagify_path_to_webp( $media_file['path'] );
+ }
+ }
+ } else {
+ $this->media_id = 0;
+ $this->old_backup_path = false;
+ $this->old_webp_paths = [];
+ }
+
+ return $new_filename;
+ }
+
+ /**
+ * Get the attachment.
+ *
+ * @since 1.6.9
+ * @since 1.9 Deprecated.
+ * @deprecated
+ *
+ * @return object A Imagify_Attachment object (or any class extending it).
+ */
+ protected function get_attachment() {
+ if ( $this->attachment ) {
+ return $this->attachment;
+ }
+
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9', '\\Imagify\\ThirdParty\\EnableMediaReplace\\Main::get_instance()->get_process()' );
+
+ $this->attachment = get_imagify_attachment( 'wp', $this->attachment_id, 'enable_media_replace' );
+
+ return $this->attachment;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-file-attachment.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-file-attachment.php
new file mode 100644
index 00000000..87a9d83f
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-file-attachment.php
@@ -0,0 +1,760 @@
+id = (int) $id;
+ $this->get_row();
+ } elseif ( is_array( $id ) || is_object( $id ) ) {
+ $prim_key = $this->get_row_db_instance()->get_primary_key();
+ $this->row = (array) $id;
+ $this->id = $this->row[ $prim_key ];
+ } else {
+ $this->invalidate_row();
+ }
+
+ $this->filesystem = Imagify_Filesystem::get_instance();
+ $this->optimization_state_transient = 'imagify-file-async-in-progress-' . $this->id;
+ }
+
+ /**
+ * Get the original file path.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_original_path() {
+ $row = $this->get_row();
+
+ if ( ! $row || empty( $row['path'] ) ) {
+ return '';
+ }
+
+ return Imagify_Files_Scan::remove_placeholder( $row['path'] );
+ }
+
+ /**
+ * Get the original file URL.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_original_url() {
+ $row = $this->get_row();
+
+ if ( ! $row || empty( $row['path'] ) ) {
+ return '';
+ }
+
+ return Imagify_Files_Scan::remove_placeholder( $row['path'], 'url' );
+ }
+
+ /**
+ * Get the backup file path, even if the file doesn't exist.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_backup_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return Imagify_Custom_Folders::get_file_backup_path( $this->get_original_path() );
+ }
+
+ /**
+ * Get the backup URL.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string|bool The file URL. False on failure.
+ */
+ public function get_backup_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return site_url( '/' ) . $this->filesystem->make_path_relative( $this->get_raw_backup_path() );
+ }
+
+ /**
+ * Get the optimization data.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_data() {
+ if ( ! $this->is_valid() ) {
+ return array();
+ }
+
+ $data = array_merge( $this->get_row_db_instance()->get_column_defaults(), $this->get_row() );
+
+ unset( $data['file_id'] );
+ return $data;
+ }
+
+ /**
+ * Get the optimization level.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int|bool
+ */
+ public function get_optimization_level() {
+ $row = $this->get_row();
+ return isset( $row['optimization_level'] ) && is_int( $row['optimization_level'] ) ? $row['optimization_level'] : false;
+ }
+
+ /**
+ * Get the file optimization status (success, already_optimized, or error).
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string
+ */
+ public function get_status() {
+ $row = $this->get_row();
+ return ! empty( $row['status'] ) ? $row['status'] : '';
+ }
+
+ /**
+ * Get width and height of the original image.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_dimensions() {
+ $row = $this->get_row();
+
+ return array(
+ 'width' => ! empty( $row['width'] ) ? $row['width'] : 0,
+ 'height' => ! empty( $row['height'] ) ? $row['height'] : 0,
+ );
+ }
+
+ /**
+ * Get the attachment error if there is one.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return string The message error
+ */
+ public function get_optimized_error() {
+ $row = $this->get_row();
+ return ! empty( $row['error'] ) && is_string( $row['error'] ) ? trim( $row['error'] ) : '';
+ }
+
+ /**
+ * Count number of optimized sizes.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return int
+ */
+ public function get_optimized_sizes_count() {
+ return $this->get_status() === 'success' ? 1 : 0;
+ }
+
+ /**
+ * Delete the data related to optimization.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_imagify_data() {
+ if ( ! $this->is_valid() ) {
+ return;
+ }
+
+ $this->update_row( $this->get_reset_imagify_data() );
+ }
+
+ /**
+ * Tell if the current file extension is supported.
+ * If it's in the DB, it's supported.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_extension_supported() {
+ return $this->is_valid();
+ }
+
+ /**
+ * Tell if the current file mime type is supported.
+ * If it's in the DB, it's supported.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function is_mime_type_supported() {
+ return $this->is_valid();
+ }
+
+ /**
+ * Tell if the current attachment has the required WP metadata.
+ * Well, these are not attachments, so...
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_required_metadata() {
+ return $this->is_valid();
+ }
+
+ /**
+ * Get the original file size.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $human_format True to display the image human format size (1Mb).
+ * @param int $decimals Precision of number of decimal places.
+ * @return string|int
+ */
+ public function get_original_size( $human_format = true, $decimals = 2 ) {
+ if ( ! $this->is_valid() ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+ $row = $this->get_row();
+
+ if ( ! empty( $row['original_size'] ) ) {
+ $size = $row['original_size'];
+ } else {
+ // Check for the backup file first.
+ $file_path = $this->get_backup_path();
+
+ if ( ! $file_path ) {
+ $file_path = $this->get_original_path();
+ $file_path = $file_path && $this->filesystem->exists( $file_path ) ? $file_path : false;
+ }
+
+ $size = $file_path ? $this->filesystem->size( $file_path ) : 0;
+ }
+
+ if ( $human_format ) {
+ return imagify_size_format( (int) $size, $decimals );
+ }
+
+ return (int) $size;
+ }
+
+ /**
+ * Get the optimized file size.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param bool $human_format True to display the image human format size (1Mb).
+ * @param int $decimals Precision of number of decimal places.
+ * @return string|int
+ */
+ public function get_optimized_size( $human_format = true, $decimals = 2 ) {
+ if ( ! $this->is_valid() ) {
+ return $human_format ? imagify_size_format( 0, $decimals ) : 0;
+ }
+
+ $row = $this->get_row();
+
+ if ( ! empty( $row['optimized_size'] ) ) {
+ $size = $row['optimized_size'];
+ } else {
+ $file_path = $this->get_original_path();
+ $file_path = $file_path && $this->filesystem->exists( $file_path ) ? $file_path : false;
+ $size = $file_path ? $this->filesystem->size( $file_path ) : 0;
+ }
+
+ if ( $human_format ) {
+ return imagify_size_format( (int) $size, $decimals );
+ }
+
+ return (int) $size;
+ }
+
+ /**
+ * Get the optimized attachment size.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return float A 2-decimals float.
+ */
+ public function get_saving_percent() {
+ if ( ! $this->is_valid() ) {
+ return round( (float) 0, 2 );
+ }
+
+ $row = $this->get_row();
+
+ if ( ! empty( $row['percent'] ) ) {
+ return $row['percent'] / 100;
+ }
+
+ $original_size = $this->get_original_size( false );
+
+ if ( ! $original_size ) {
+ return round( (float) 0, 2 );
+ }
+
+ $optimized_size = $this->get_optimized_size( false );
+
+ if ( ! $optimized_size ) {
+ return round( (float) 0, 2 );
+ }
+
+ return round( ( $original_size - $optimized_size ) / $original_size * 100, 2 );
+ }
+
+ /**
+ * Get the overall optimized size (all thumbnails).
+ * And since we don't have thumbnails...
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return float A 2-decimals float.
+ */
+ public function get_overall_saving_percent() {
+ return $this->get_saving_percent();
+ }
+
+ /**
+ * Get the statistics of the file.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The thumbnail slug. Not used here.
+ * @param string $key The specific data slug.
+ * @return array|string
+ */
+ public function get_size_data( $size = null, $key = null ) {
+ if ( ! isset( $key ) ) {
+ $key = $size;
+ }
+
+ if ( ! $this->is_valid() ) {
+ return isset( $key ) ? '' : array();
+ }
+
+ $data = imagify_merge_intersect( $this->get_row(), array(
+ 'original_size' => 0,
+ 'optimized_size' => false,
+ 'percent' => 0,
+ 'status' => false,
+ 'error' => false,
+ ) );
+
+ $data['success'] = 'success' === $data['status'];
+
+ if ( $data['status'] ) {
+ unset( $data['status'], $data['error'] );
+
+ if ( empty( $data['percent'] ) && $data['original_size'] && $data['optimized_size'] ) {
+ $data['percent'] = round( ( $data['original_size'] - $data['optimized_size'] ) / $data['original_size'] * 100, 2 );
+ } elseif ( ! empty( $data['percent'] ) ) {
+ $data['percent'] = $data['percent'] / 100;
+ }
+ } else {
+ unset( $data['status'], $data['original_size'], $data['optimized_size'], $data['percent'] );
+ }
+
+ if ( isset( $key ) ) {
+ return isset( $data[ $key ] ) ? $data[ $key ] : '';
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get the global statistics data or a specific one.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $key The specific data slug.
+ * @return array|string
+ */
+ public function get_stats_data( $key = null ) {
+ if ( ! $this->is_valid() ) {
+ return isset( $key ) ? '' : array();
+ }
+
+ $stats = $this->get_size_data( $key );
+ $default = array(
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ );
+
+ return imagify_merge_intersect( $stats, $default );
+ }
+
+ /**
+ * Since these files are not resized, this method is not needed.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function update_metadata_size() {
+ return false;
+ }
+
+ /**
+ * Get the unoptimized sizes for a specific attachment.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_unoptimized_sizes() {
+ return array();
+ }
+
+ /**
+ * Get default values used to reset Imagify data.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function get_reset_imagify_data() {
+ static $column_defaults;
+
+ if ( ! isset( $column_defaults ) ) {
+ $column_defaults = $this->get_row_db_instance()->get_column_defaults();
+
+ // All DB columns that have `null` as default value, are Imagify data.
+ foreach ( $column_defaults as $column_name => $value ) {
+ if ( 'hash' === $column_name || 'modified' === $column_name ) {
+ continue;
+ }
+
+ if ( isset( $value ) ) {
+ unset( $column_defaults[ $column_name ] );
+ }
+ }
+ }
+
+ $imagify_columns = $column_defaults;
+
+ // Also set the new file hash.
+ $file_path = $this->get_original_path();
+
+ if ( $file_path && $this->filesystem->exists( $file_path ) ) {
+ $imagify_columns['hash'] = md5_file( $file_path );
+ }
+
+ return $imagify_columns;
+ }
+
+ /**
+ * Fills statistics data with values from $data array.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param string $size The attachment size key. Not used here.
+ * @return bool|array False if the original size has an error or an array contains the data for other result.
+ */
+ public function fill_data( $data, $response, $size = null ) {
+ $data = is_array( $data ) ? $data : array();
+ $data = imagify_merge_intersect( $data, $this->get_reset_imagify_data() );
+
+ if ( is_wp_error( $response ) ) {
+ // Error or already optimized.
+ $data['error'] = $response->get_error_message();
+
+ if ( false !== strpos( $data['error'], 'This image is already compressed' ) ) {
+ $data['status'] = 'already_optimized';
+ } else {
+ $data['status'] = 'error';
+ }
+
+ return $data;
+ }
+
+ // Success.
+ $old_data = $this->get_data();
+ $original_size = $old_data['original_size'];
+ $data['percent'] = 0;
+ $data['status'] = 'success';
+
+ if ( ! empty( $response->original_size ) && ! $original_size ) {
+ $data['original_size'] = (int) $response->original_size;
+ $original_size = $data['original_size'];
+ }
+
+ if ( ! empty( $response->new_size ) ) {
+ $data['optimized_size'] = (int) $response->new_size;
+ } else {
+ $file_path = $this->get_original_path();
+ $file_path = $file_path && $this->filesystem->exists( $file_path ) ? $file_path : false;
+
+ $data['optimized_size'] = $file_path ? $this->filesystem->size( $file_path ) : 0;
+ }
+
+ if ( $original_size && $data['optimized_size'] ) {
+ $data['percent'] = round( ( $original_size - $data['optimized_size'] ) / $original_size * 10000 );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Optimize the file with Imagify.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @param array $metadata The attachment meta data. Not used here.
+ * @return bool|object True on success (status success or already_optimized). A WP_Error object on failure.
+ */
+ public function optimize( $optimization_level = null, $metadata = null ) {
+ // Check if the file extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
+ }
+
+ $optimization_level = is_numeric( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
+
+ // Check if the image is already optimized.
+ if ( $this->is_optimized() && ( $this->get_optimization_level() === $optimization_level ) ) {
+ return new WP_Error( 'same_optimization_level', __( 'This file is already optimized with this level.', 'imagify' ) );
+ }
+
+ /**
+ * Fires before optimizing a file.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param int $id The file ID.
+ */
+ do_action( 'before_imagify_optimize_file', $this->id );
+
+ $this->set_running_status();
+
+ // Optimize the image.
+ $response = do_imagify( $this->get_original_path(), array(
+ 'optimization_level' => $optimization_level,
+ 'context' => 'File',
+ 'original_size' => $this->get_original_size( false ),
+ 'backup_path' => $this->get_raw_backup_path(),
+ ) );
+
+ // Fill the data.
+ $data = $this->fill_data( array(
+ 'optimization_level' => $optimization_level,
+ ), $response );
+
+ // Save the data.
+ $this->update_row( $data );
+
+ if ( is_wp_error( $response ) ) {
+ $this->delete_running_status();
+
+ if ( 'error' === $data['status'] ) {
+ return $response;
+ }
+
+ // Already optimized.
+ return true;
+ }
+
+ /**
+ * Fires after optimizing an attachment.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param int $id The attachment ID.
+ * @param array $optimized_data The optimization data.
+ */
+ do_action( 'after_imagify_optimize_file', $this->id, $this->get_data() );
+
+ $this->delete_running_status();
+
+ return true;
+ }
+
+ /**
+ * Process an attachment restoration from the backup file.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return bool|object True on success (status success or already_optimized). A WP_Error object on failure.
+ */
+ public function restore() {
+ // Check if the file extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
+ }
+
+ $backup_path = $this->get_backup_path();
+
+ // Stop the process if there is no backup file to restore.
+ if ( ! $backup_path ) {
+ return new WP_Error( 'source_doesnt_exist', __( 'The backup file does not exist.', 'imagify' ) );
+ }
+
+ $file_path = $this->get_original_path();
+
+ if ( ! $file_path ) {
+ return new WP_Error( 'empty_path', __( 'The file path is empty.', 'imagify' ) );
+ }
+
+ /**
+ * Fires before restoring a file.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param int $id The file ID.
+ */
+ do_action( 'before_imagify_restore_file', $this->id );
+
+ // Create the original image from the backup.
+ $this->filesystem->copy( $backup_path, $file_path, true );
+ $this->filesystem->chmod_file( $file_path );
+
+ // Remove old optimization data.
+ $this->delete_imagify_data();
+
+ /**
+ * Fires after restoring a file.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param int $id The file ID.
+ */
+ do_action( 'after_imagify_restore_file', $this->id );
+ }
+
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** DB ROW ================================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Invalidate the row.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return array The row
+ */
+ public function invalidate_row() {
+ // Since the ID doesn't exist in any other table (not a Post ID, not a NGG gallery ID), it must be reset.
+ $this->id = 0;
+ $this->row = array();
+ return $this->row;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-ngg-attachment.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-ngg-attachment.php
new file mode 100644
index 00000000..24c82012
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-ngg-attachment.php
@@ -0,0 +1,1011 @@
+is_mime_type_supported()
+ */
+ protected $is_mime_type_supported;
+
+ /**
+ * The constructor.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param int|object $id An image attachment ID or a NGG object.
+ */
+ public function __construct( $id ) {
+ imagify_deprecated_class( get_class( $this ), '1.9', '\\Imagify\\ThirdParty\\NGG\\Optimization\\Process\\NGG( $id )' );
+
+ if ( is_object( $id ) ) {
+ if ( $id instanceof nggImage ) {
+ $this->image = $id;
+ $this->id = (int) $id->pid;
+ } else {
+ $this->image = nggdb::find_image( (int) $id->pid );
+ $this->id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
+ }
+ } else {
+ $this->image = nggdb::find_image( absint( $id ) );
+ $this->id = ! empty( $this->image->pid ) ? (int) $this->image->pid : 0;
+ }
+
+ $this->get_row();
+
+ if ( ! empty( $this->image->_ngiw ) ) {
+ $this->storage = $this->image->_ngiw->get_storage()->object;
+ } else {
+ $this->storage = C_Gallery_Storage::get_instance()->object;
+ }
+
+ $this->filesystem = Imagify_Filesystem::get_instance();
+ $this->optimization_state_transient = 'imagify-ngg-async-in-progress-' . $this->id;
+
+ // Load nggAdmin class.
+ $ngg_admin_functions_path = WP_PLUGIN_DIR . '/' . NGGFOLDER . '/products/photocrati_nextgen/modules/ngglegacy/admin/functions.php';
+
+ if ( ! class_exists( 'nggAdmin' ) && $this->filesystem->exists( $ngg_admin_functions_path ) ) {
+ require_once $ngg_admin_functions_path;
+ }
+ }
+
+ /**
+ * Get the original attachment path.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @access public
+ * @return string
+ */
+ public function get_original_path() {
+ if ( ! $this->is_valid() ) {
+ return '';
+ }
+
+ return $this->image->imagePath;
+ }
+
+ /**
+ * Get the original attachment URL.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @access public
+ * @return string
+ */
+ public function get_original_url() {
+ if ( ! $this->is_valid() ) {
+ return '';
+ }
+
+ return $this->image->imageURL;
+ }
+
+ /**
+ * Get the attachment backup file path, even if the file doesn't exist.
+ *
+ * @since 1.6.13
+ * @author Grégory Viguier
+ * @access public
+ *
+ * @return string|bool The file path. False on failure.
+ */
+ public function get_raw_backup_path() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return get_imagify_ngg_attachment_backup_path( $this->get_original_path() );
+ }
+
+ /**
+ * Get the attachment backup URL.
+ *
+ * @since 1.6.8
+ * @author Grégory Viguier
+ *
+ * @return string|false
+ */
+ public function get_backup_url() {
+ if ( ! $this->is_valid() ) {
+ return false;
+ }
+
+ return site_url( '/' ) . $this->filesystem->make_path_relative( $this->get_raw_backup_path() );
+ }
+
+ /**
+ * Get the attachment optimization data.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @access public
+ * @return array
+ */
+ public function get_data() {
+ $row = $this->get_row();
+ return isset( $row['data'] ) ? $row['data'] : array();
+ }
+
+ /**
+ * Get the attachment optimization level.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @access public
+ * @return int|bool
+ */
+ public function get_optimization_level() {
+ $row = $this->get_row();
+ return isset( $row['optimization_level'] ) ? (int) $row['optimization_level'] : false;
+ }
+
+ /**
+ * Get the attachment optimization status (success or error).
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @access public
+ * @return string|bool
+ */
+ public function get_status() {
+ $row = $this->get_row();
+ return isset( $row['status'] ) ? $row['status'] : false;
+ }
+
+ /**
+ * Delete the data related to optimization.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ */
+ public function delete_imagify_data() {
+ if ( ! $this->get_row() ) {
+ return;
+ }
+
+ $this->delete_row();
+ }
+
+ /**
+ * Get width and height of the original image.
+ *
+ * @since 1.7
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_dimensions() {
+ return array(
+ 'width' => ! empty( $this->image->meta_data['width'] ) ? (int) $this->image->meta_data['width'] : 0,
+ 'height' => ! empty( $this->image->meta_data['height'] ) ? (int) $this->image->meta_data['height'] : 0,
+ );
+ }
+
+ /**
+ * Get the file mime type + file extension (if the file is supported).
+ *
+ * @since 1.8
+ * @access public
+ * @see wp_check_filetype()
+ * @author Grégory Viguier
+ *
+ * @return object
+ */
+ public function get_file_type() {
+ if ( isset( $this->file_type ) ) {
+ return $this->file_type;
+ }
+
+ if ( ! $this->is_valid() ) {
+ $this->file_type = (object) array(
+ 'ext' => '',
+ 'type' => '',
+ );
+ return $this->file_type;
+ }
+
+ $this->file_type = (object) wp_check_filetype( $this->get_original_path(), imagify_get_mime_types( 'image' ) );
+
+ return $this->file_type;
+ }
+
+ /**
+ * Tell if the current attachment has the required WP metadata.
+ *
+ * @since 1.6.12
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ public function has_required_metadata() {
+ static $sizes;
+
+ if ( ! isset( $sizes ) ) {
+ $sizes = $this->get_image_sizes();
+ }
+
+ return $sizes && $this->get_original_path();
+ }
+
+ /**
+ * Update the metadata size of the attachment.
+ *
+ * @since 1.5
+ *
+ * @access public
+ * @return void
+ */
+ public function update_metadata_size() {
+ $size = $this->filesystem->get_image_size( $this->get_original_path() );
+
+ if ( ! $size ) {
+ return;
+ }
+
+ $this->image->meta_data['width'] = $size['width'];
+ $this->image->meta_data['height'] = $size['height'];
+ $this->image->meta_data['full']['width'] = $size['width'];
+ $this->image->meta_data['full']['height'] = $size['height'];
+
+ nggdb::update_image_meta( $this->id, $this->image->meta_data );
+ }
+
+ /**
+ * Fills statistics data with values from $data array.
+ *
+ * @since 1.5
+ * @since 1.6.5 Not static anymore.
+ * @since 1.6.6 Removed the attachment ID parameter.
+ * @since 1.7 Removed the image URL parameter.
+ * @author Jonathan Buttigieg
+ * @access public
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param string $size The attachment size key.
+ * @return bool|array False if the original size has an error or an array contains the data for other result.
+ */
+ public function fill_data( $data, $response, $size = 'full' ) {
+ $data = is_array( $data ) ? $data : array();
+ $data['sizes'] = ! empty( $data['sizes'] ) && is_array( $data['sizes'] ) ? $data['sizes'] : array();
+
+ if ( empty( $data['stats'] ) ) {
+ $data['stats'] = array(
+ 'original_size' => 0,
+ 'optimized_size' => 0,
+ 'percent' => 0,
+ );
+ }
+
+ if ( is_wp_error( $response ) ) {
+ // Error or already optimized.
+ $error = $response->get_error_message();
+ $error_status = 'error';
+
+ $data['sizes'][ $size ] = array(
+ 'success' => false,
+ 'error' => $error,
+ );
+
+ // Update the error status for the original size.
+ if ( 'full' === $size ) {
+ if ( false !== strpos( $error, 'This image is already compressed' ) ) {
+ $error_status = 'already_optimized';
+ }
+
+ $this->update_row( array(
+ // The pid column is needed in case the row doesn't exist yet.
+ 'pid' => $this->id,
+ 'status' => $error_status,
+ 'data' => $data,
+ ) );
+
+ return false;
+ }
+
+ return $data;
+ }
+
+ // Success.
+ $old_data = $this->get_data();
+ $original_size = ! empty( $old_data['sizes'][ $size ]['original_size'] ) ? (int) $old_data['sizes'][ $size ]['original_size'] : 0;
+
+ $response = (object) array_merge( array(
+ 'original_size' => 0,
+ 'new_size' => 0,
+ 'percent' => 0,
+ ), (array) $response );
+
+ if ( ! empty( $response->original_size ) && ! $original_size ) {
+ $original_size = (int) $response->original_size;
+ }
+
+ if ( ! empty( $response->new_size ) ) {
+ $optimized_size = (int) $response->new_size;
+ } else {
+ $file_path = $this->get_original_path();
+ $file_path = $file_path && $this->filesystem->exists( $file_path ) ? $file_path : false;
+ $optimized_size = $file_path ? $this->filesystem->size( $file_path ) : 0;
+ }
+
+ if ( $original_size && $optimized_size ) {
+ $percent = round( ( $original_size - $optimized_size ) / $original_size * 100, 2 );
+ } elseif ( ! empty( $response->percent ) ) {
+ $percent = round( $response->percent, 2 );
+ } else {
+ $percent = 0;
+ }
+
+ $data['sizes'][ $size ] = array(
+ 'success' => true,
+ 'original_size' => $original_size,
+ 'optimized_size' => $optimized_size,
+ 'percent' => $percent,
+ );
+
+ $data['stats']['original_size'] += $original_size;
+ $data['stats']['optimized_size'] += $optimized_size;
+ $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 );
+
+ return $data;
+ }
+
+ /**
+ * Optimize all sizes with Imagify.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @access public
+ * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @param array $metadata The attachment meta data (not used here).
+ * @return array $data The optimization data.
+ */
+ public function optimize( $optimization_level = null, $metadata = array() ) {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return;
+ }
+
+ $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
+
+ // To avoid issue with "original_size" at 0 in "_imagify_data".
+ if ( 0 === (int) $this->get_stats_data( 'original_size' ) ) {
+ $this->delete_imagify_data();
+ }
+
+ // Check if the full size is already optimized.
+ if ( $this->is_optimized() && $this->get_optimization_level() === $optimization_level ) {
+ return;
+ }
+
+ // Get file path for original image.
+ $attachment_path = $this->get_original_path();
+ $attachment_url = $this->get_original_url();
+
+ /**
+ * Fires before optimizing an attachment.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param int $id The image ID
+ */
+ do_action( 'before_imagify_ngg_optimize_attachment', $this->id );
+
+ $this->set_running_status();
+
+ // Optimize the original size.
+ $response = do_imagify( $attachment_path, array(
+ 'optimization_level' => $optimization_level,
+ 'context' => 'NGG',
+ 'keep_exif' => true,
+ 'original_size' => $this->get_original_size( false ),
+ 'backup_path' => $this->get_raw_backup_path(),
+ ) );
+
+ $data = $this->fill_data( null, $response );
+
+ /**
+ * Filter the optimization data of the full size.
+ *
+ * @since 1.8
+ * @author Grégory Viguier
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param int $id The attachment ID.
+ * @param string $attachment_path The attachment path.
+ * @param string $attachment_url The attachment URL.
+ * @param string $size_key The attachment size key. The value is obviously 'full' but it's kept for concistancy with other filters.
+ * @param int $optimization_level The optimization level.
+ */
+ $data = apply_filters( 'imagify_fill_ngg_full_size_data', $data, $response, $this->id, $attachment_path, $attachment_url, 'full', $optimization_level );
+
+ // Save the optimization level.
+ $this->update_row( array(
+ // The pid column is needed in case the row doesn't exist yet.
+ 'pid' => $this->id,
+ 'optimization_level' => $optimization_level,
+ ) );
+
+ if ( ! $data ) {
+ // Error or already optimized.
+ $this->delete_running_status();
+ return;
+ }
+
+ // Optimize thumbnails.
+ $data = $this->optimize_thumbnails( $optimization_level, $data );
+
+ // Save the status to success.
+ $this->update_row( array(
+ 'status' => 'success',
+ ) );
+
+ /**
+ * Update NGG meta data.
+ */
+ $image_data = $this->storage->_image_mapper->find( $this->id );
+
+ if ( ! $image_data ) {
+ $this->delete_running_status();
+ return $data;
+ }
+
+ $dimensions = $this->filesystem->get_image_size( $attachment_path );
+ $md5 = md5_file( $attachment_path );
+
+ if ( ( $dimensions || $md5 ) && ( empty( $image_data->meta_data['full'] ) || ! is_array( $image_data->meta_data['full'] ) ) ) {
+ $image_data->meta_data['full'] = array(
+ 'width' => 0,
+ 'height' => 0,
+ 'md5' => '',
+ );
+ }
+
+ if ( $dimensions ) {
+ $image_data->meta_data['width'] = $dimensions['width'];
+ $image_data->meta_data['height'] = $dimensions['height'];
+ $image_data->meta_data['full']['width'] = $dimensions['width'];
+ $image_data->meta_data['full']['height'] = $dimensions['height'];
+ }
+
+ if ( $md5 ) {
+ $image_data->meta_data['md5'] = $md5;
+ $image_data->meta_data['full']['md5'] = $md5;
+ }
+
+ /**
+ * Fires after optimizing an attachment.
+ *
+ * @since 1.5
+ *
+ * @param int $id The attachment ID.
+ * @param array $data The optimization data.
+ */
+ do_action( 'after_imagify_ngg_optimize_attachment', $this->id, $data );
+
+ $this->delete_running_status();
+
+ return $data;
+ }
+
+ /**
+ * Optimize all thumbnails of an image.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @access public
+ * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @param array $data The optimization data.
+ * @return array $data The optimization data.
+ */
+ public function optimize_thumbnails( $optimization_level = null, $data = array() ) {
+ $sizes = $this->get_image_sizes();
+ $data = $data ? $data : $this->get_data();
+
+ // Stop if the original image has an error.
+ if ( $this->has_error() ) {
+ return $data;
+ }
+
+ $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
+
+ /**
+ * Fires before optimizing all thumbnails.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param int $id The image ID.
+ */
+ do_action( 'before_imagify_ngg_optimize_thumbnails', $this->id );
+
+ if ( $sizes ) {
+ $image_data = $this->storage->_image_mapper->find( $this->id );
+
+ foreach ( $sizes as $size_key ) {
+ if ( 'full' === $size_key || isset( $data['sizes'][ $size_key ]['success'] ) ) {
+ continue;
+ }
+
+ $thumbnail_path = $this->storage->get_image_abspath( $image_data, $size_key );
+ $thumbnail_url = $this->storage->get_image_url( $image_data, $size_key );
+
+ // Optimize the thumbnail size.
+ $response = do_imagify( $thumbnail_path, array(
+ 'optimization_level' => $optimization_level,
+ 'context' => 'NGG',
+ 'keep_exif' => true,
+ 'backup' => false,
+ ) );
+
+ $data = $this->fill_data( $data, $response, $size_key );
+
+ /**
+ * Filter the optimization data of a specific thumbnail.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param array $data The statistics data.
+ * @param object $response The API response.
+ * @param int $id The image ID.
+ * @param string $thumbnail_path The image path.
+ * @param string $thumbnail_url The image URL.
+ * @param string $size_key The image size key.
+ * @param bool $is_aggressive The optimization level.
+ * @return array $data The new optimization data.
+ */
+ $data = apply_filters( 'imagify_fill_ngg_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level );
+ }
+
+ $this->update_row( array(
+ 'data' => $data,
+ ) );
+ } // End if().
+
+ /**
+ * Fires after optimizing all thumbnails.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param int $id The image ID.
+ * @param array $data The optimization data.
+ */
+ do_action( 'after_imagify_ngg_optimize_thumbnails', $this->id, $data );
+
+ return $data;
+ }
+
+ /**
+ * Optimize one size.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param string $size The thumbnail size.
+ */
+ public function optimize_new_thumbnail( $size ) {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return;
+ }
+
+ if ( ! $this->is_optimized() ) {
+ // The main image is not optimized.
+ return;
+ }
+
+ $data = $this->get_data();
+
+ if ( isset( $data['sizes'][ $size ]['success'] ) ) {
+ // This thumbnail has already been processed.
+ return;
+ }
+
+ $sizes = $this->get_image_sizes();
+ $sizes = array_flip( $sizes );
+
+ if ( ! isset( $sizes[ $size ] ) ) {
+ // This size doesn't exist.
+ return;
+ }
+
+ /**
+ * Fires before optimizing a thumbnail.
+ *
+ * @since 1.8
+ * @author Grégory Viguier
+ *
+ * @param int $id The image ID.
+ */
+ do_action( 'before_imagify_ngg_optimize_new_thumbnail', $this->id );
+
+ $this->set_running_status();
+
+ $image_data = $this->storage->_image_mapper->find( $this->id );
+ $thumbnail_path = $this->storage->get_image_abspath( $image_data, $size );
+ $thumbnail_url = $this->storage->get_image_url( $image_data, $size );
+ $optimization_level = $this->get_optimization_level();
+
+ // Optimize the thumbnail size.
+ $response = do_imagify( $thumbnail_path, array(
+ 'optimization_level' => $optimization_level,
+ 'context' => 'NGG',
+ 'keep_exif' => true,
+ 'backup' => false,
+ ) );
+
+ $data = $this->fill_data( $data, $response, $size );
+
+ /** This filter is documented in inc/3rd-party/nextgen-gallery/inc/classes/class-imagify-ngg-attachment.php. */
+ $data = apply_filters( 'imagify_fill_ngg_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size, $optimization_level );
+
+ $this->update_row( array(
+ 'data' => $data,
+ ) );
+
+ /**
+ * Fires after optimizing a thumbnail.
+ *
+ * @since 1.8
+ * @author Grégory Viguier
+ *
+ * @param int $id The image ID.
+ * @param array $data The optimization data.
+ */
+ do_action( 'after_imagify_ngg_optimize_new_thumbnail', $this->id, $data );
+
+ $this->delete_running_status();
+ }
+
+ /**
+ * Re-optimize the given thumbnail sizes to the same level.
+ * This is not used in this context.
+ *
+ * @since 1.7.1
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $sizes The sizes to optimize.
+ * @return array|void A WP_Error object on failure.
+ */
+ public function reoptimize_thumbnails( $sizes ) {}
+
+ /**
+ * Process an attachment restoration from the backup file.
+ *
+ * @since 1.5
+ * @since 1.6.9 Doesn't use NGG's recover_image() anymore, these are fundamentally not the same things. This also prevents alt text, description, and tags deletion.
+ * @since 1.6.9 Return true or a WP_Error object.
+ * @author Jonathan Buttigieg
+ *
+ * @access public
+ * @return bool|object True on success, a WP_Error object on error.
+ */
+ public function restore() {
+ // Check if the attachment extension is allowed.
+ if ( ! $this->is_extension_supported() ) {
+ return new WP_Error( 'mime_not_type_supported', __( 'Mime type not supported.', 'imagify' ) );
+ }
+
+ // Stop the process if there is no backup file to restore.
+ if ( ! $this->has_backup() ) {
+ return new WP_Error( 'no_backup', __( 'Backup image not found.', 'imagify' ) );
+ }
+
+ $image_data = $this->storage->_image_mapper->find( $this->id );
+
+ if ( ! $image_data ) {
+ return new WP_Error( 'no_image', __( 'Image not found in NextGen Gallery data.', 'imagify' ) );
+ }
+
+ /**
+ * Make some more tests before restoring the backup.
+ */
+ $full_abspath = $this->storage->get_image_abspath( $image_data );
+ $backup_abspath = $this->storage->get_image_abspath( $image_data, 'backup' );
+
+ if ( $backup_abspath === $full_abspath ) {
+ return new WP_Error( 'same_path', __( 'Image path and backup path are identical.', 'imagify' ) );
+ }
+
+ if ( ! $this->filesystem->is_writable( $full_abspath ) || ! $this->filesystem->is_writable( $this->filesystem->dir_path( $full_abspath ) ) ) {
+ return new WP_Error( 'destination_not_writable', __( 'The image to replace is not writable.', 'imagify' ) );
+ }
+
+ /**
+ * Fires before restoring an attachment.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param int $id The attachment ID.
+ */
+ do_action( 'before_imagify_ngg_restore_attachment', $this->id );
+
+ if ( ! $this->filesystem->copy( $backup_abspath, $full_abspath, true, FS_CHMOD_FILE ) ) {
+ return new WP_Error( 'copy_failed', __( 'Restoration failed.', 'imagify' ) );
+ }
+
+ /**
+ * Remove Imagify data.
+ */
+ $this->delete_row();
+
+ /**
+ * Fill in the NGG meta data.
+ */
+ // 1- Meta data for the backup file.
+ $dimensions = $this->filesystem->get_image_size( $backup_abspath );
+ $backup_data = array(
+ 'backup' => array(
+ 'filename' => $this->filesystem->file_name( $full_abspath ), // Yes, $full_abspath.
+ 'width' => 0,
+ 'height' => 0,
+ 'generated' => microtime(),
+ ),
+ );
+
+ if ( $dimensions ) {
+ $backup_data['backup']['width'] = $dimensions['width'];
+ $backup_data['backup']['height'] = $dimensions['height'];
+ }
+
+ // 2- Meta data for the full sized image.
+ $full_data = array(
+ 'width' => 0,
+ 'height' => 0,
+ 'md5' => '',
+ 'full' => array(
+ 'width' => 0,
+ 'height' => 0,
+ 'md5' => '',
+ ),
+ );
+
+ $dimensions = $this->filesystem->get_image_size( $full_abspath );
+
+ if ( $dimensions ) {
+ $full_data['width'] = $dimensions['width'];
+ $full_data['height'] = $dimensions['height'];
+ $full_data['full']['width'] = $dimensions['width'];
+ $full_data['full']['height'] = $dimensions['height'];
+ }
+
+ $md5 = md5_file( $full_abspath );
+
+ if ( $md5 ) {
+ $full_data['md5'] = $md5;
+ $full_data['full']['md5'] = $md5;
+ }
+
+ // 3- Thumbnails meta data.
+ $thumbnails_data = array();
+
+ // 4- Common meta data.
+ require_once NGGALLERY_ABSPATH . '/lib/meta.php';
+ $meta_obj = new nggMeta( $image_data );
+ $common_data = $meta_obj->get_common_meta();
+
+ if ( $common_data ) {
+ unset( $common_data['width'], $common_data['height'] );
+ } else {
+ $common_data = array(
+ 'aperture' => 0,
+ 'credit' => '',
+ 'camera' => '',
+ 'caption' => '',
+ 'created_timestamp' => 0,
+ 'copyright' => '',
+ 'focal_length' => 0,
+ 'iso' => 0,
+ 'shutter_speed' => 0,
+ 'flash' => 0,
+ 'title' => '',
+ 'keywords' => '',
+ );
+
+ if ( ! empty( $image_data->meta_data ) && is_array( $image_data->meta_data ) ) {
+ $image_data->meta_data = array_merge( $common_data, $image_data->meta_data );
+ $common_data = array_intersect_key( $image_data->meta_data, $common_data );
+ }
+ }
+
+ $common_data['saved'] = true;
+
+ /**
+ * Re-create non-fullsize image sizes and add related data.
+ */
+ $failed = array();
+
+ foreach ( $this->get_image_sizes() as $named_size ) {
+ if ( 'full' === $named_size ) {
+ continue;
+ }
+
+ $params = $this->storage->get_image_size_params( $image_data, $named_size );
+ $thumbnail = $this->storage->generate_image_clone(
+ $backup_abspath,
+ $this->storage->get_image_abspath( $image_data, $named_size ),
+ $params
+ );
+
+ if ( ! $thumbnail ) {
+ // Failed.
+ $failed[] = $named_size;
+ continue;
+ }
+
+ $size_meta = array(
+ 'width' => 0,
+ 'height' => 0,
+ 'filename' => M_I18n::mb_basename( $thumbnail->fileName ),
+ 'generated' => microtime(),
+ );
+
+ $dimensions = $this->filesystem->get_image_size( $thumbnail->fileName );
+
+ if ( $dimensions ) {
+ $size_meta['width'] = $dimensions['width'];
+ $size_meta['height'] = $dimensions['height'];
+ }
+
+ if ( isset( $params['crop_frame'] ) ) {
+ $size_meta['crop_frame'] = $params['crop_frame'];
+ }
+
+ $thumbnails_data[ $named_size ] = $size_meta;
+ } // End foreach().
+
+ do_action( 'ngg_recovered_image', $image_data );
+
+ /**
+ * Save the meta data.
+ */
+ $image_data->meta_data = array_merge( $backup_data, $full_data, $thumbnails_data, $common_data );
+
+ // Keep our property up to date.
+ $this->image->_ngiw->_cache['meta_data'] = $image_data->meta_data;
+ $this->image->_ngiw->_orig_image = $image_data;
+
+ $post_id = $this->storage->_image_mapper->save( $image_data );
+
+ if ( ! $post_id ) {
+ return new WP_Error( 'meta_data_not_saved', __( 'Related data could not be saved.', 'imagify' ) );
+ }
+
+ if ( $failed ) {
+ return new WP_Error(
+ 'thumbnail_restore_failed',
+ sprintf( _n( '%n thumbnail could not be restored.', '%n thumbnails could not be restored.', count( $failed ), 'imagify' ), count( $failed ) ),
+ array( 'failed_thumbnails' => $failed )
+ );
+ }
+
+ /**
+ * Fires after restoring an attachment.
+ *
+ * @since 1.5
+ * @author Jonathan Buttigieg
+ *
+ * @param int $id The attachment ID.
+ */
+ do_action( 'after_imagify_ngg_restore_attachment', $this->id );
+
+ return true;
+ }
+
+ /**
+ * Get the image sizes.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+ public function get_image_sizes() {
+ $sizes = array(
+ 'full',
+ );
+
+ // Remove common values (that have no value for us here, lol).
+ $image_data = array_diff_key( $this->image->meta_data, array(
+ 'backup' => 1,
+ 'width' => 1,
+ 'height' => 1,
+ 'md5' => 1,
+ 'full' => 1,
+ 'aperture' => 1,
+ 'credit' => 1,
+ 'camera' => 1,
+ 'caption' => 1,
+ 'created_timestamp' => 1,
+ 'copyright' => 1,
+ 'focal_length' => 1,
+ 'iso' => 1,
+ 'shutter_speed' => 1,
+ 'flash' => 1,
+ 'title' => 1,
+ 'keywords' => 1,
+ 'saved' => 1,
+ ) );
+
+ if ( ! $image_data ) {
+ return $sizes;
+ }
+
+ foreach ( $image_data as $size_name => $size_data ) {
+ if ( isset( $size_data['width'], $size_data['height'], $size_data['filename'], $size_data['generated'] ) ) {
+ $sizes[] = $size_name;
+ }
+ }
+
+ return $sizes;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-ngg-dynamic-thumbnails-background-process.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-ngg-dynamic-thumbnails-background-process.php
new file mode 100644
index 00000000..2b872112
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-ngg-dynamic-thumbnails-background-process.php
@@ -0,0 +1,167 @@
+data[ $key ] = $data;
+
+ return $this;
+ }
+
+ /**
+ * Dispatch.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @return array|WP_Error
+ */
+ public function dispatch() {
+ if ( ! empty( $this->data ) ) {
+ return parent::dispatch();
+ }
+ }
+
+ /**
+ * Tell if a task is already in the queue.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $data {
+ * The data to test against the queue.
+ *
+ * @type int $id The image ID. Required.
+ * @type string $size The thumbnail size. Required.
+ * }
+ * @return bool
+ */
+ public function is_in_queue( $data ) {
+ $key = $data['id'] . '|' . $data['size'];
+
+ return isset( $this->data[ $key ] );
+ }
+
+ /**
+ * Task: optimize the thumbnail.
+ *
+ * @since 1.8
+ * @access public
+ * @author Grégory Viguier
+ *
+ * @param array $item {
+ * The data to test against the queue.
+ *
+ * @type int $id The image ID. Required.
+ * @type string $size The thumbnail size. Required.
+ * }
+ * @return bool False to remove the item from the queue.
+ */
+ protected function task( $item ) {
+ $attachment_id = absint( $item['id'] );
+ $size = sanitize_text_field( $item['size'] );
+
+ if ( ! $attachment_id || ! $size ) {
+ return false;
+ }
+
+ $attachment = get_imagify_attachment( 'NGG', $attachment_id, 'ngg_optimize_dynamic_thumbnail' );
+
+ $attachment->optimize_new_thumbnail( $size );
+
+ return false;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-notices-deprecated.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-notices-deprecated.php
new file mode 100644
index 00000000..edb50d5b
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-notices-deprecated.php
@@ -0,0 +1,29 @@
+print_template( \'notice-\' . $view, $data )' );
+
+ Imagify_Views::get_instance()->print_template( 'notice-' . $view, $data );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-regenerate-thumbnails-deprecated.php b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-regenerate-thumbnails-deprecated.php
new file mode 100644
index 00000000..198a0fca
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/classes/class-imagify-regenerate-thumbnails-deprecated.php
@@ -0,0 +1,160 @@
+is_valid() || ! $attachment->is_image() ) {
+ wp_send_json_error();
+ }
+
+ // Optimize.
+ $attachment->reoptimize_thumbnails( wp_unslash( $_POST['sizes'] ) );
+
+ // Put the optimized original file back.
+ $this->put_optimized_file_back( $attachment_id );
+
+ wp_send_json_success();
+ }
+
+ /**
+ * Set the Imagify attachment.
+ *
+ * @since 1.7.1
+ * @since 1.9 Deprecated.
+ * @access protected
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param int $attachment_id Attachment ID.
+ * @return object|false An Imagify attachment object. False on failure.
+ */
+ protected function set_attachment( $attachment_id ) {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9', '\\Imagify\\ThirdParty\\RegenerateThumbnails\\Main::get_instance()->set_process()' );
+
+ if ( ! $attachment_id || ! Imagify_Requirements::is_api_key_valid() ) {
+ return false;
+ }
+
+ $attachment = get_imagify_attachment( 'wp', $attachment_id, 'regenerate_thumbnails' );
+
+ if ( ! $attachment->is_valid() || ! $attachment->is_image() || ! $attachment->is_optimized() ) {
+ return false;
+ }
+
+ // This attachment can be optimized.
+ $this->attachments[ $attachment_id ] = $attachment;
+ return $this->attachments[ $attachment_id ];
+ }
+
+ /**
+ * Unset the Imagify attachment.
+ *
+ * @since 1.7.1
+ * @since 1.9 Deprecated.
+ * @access protected
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param int $attachment_id Attachment ID.
+ */
+ protected function unset_attachment( $attachment_id ) {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9', '\\Imagify\\ThirdParty\\RegenerateThumbnails\\Main::get_instance()->unset_process()' );
+
+ unset( $this->attachments[ $attachment_id ] );
+ }
+
+ /**
+ * Get the Imagify attachment.
+ *
+ * @since 1.7.1
+ * @since 1.9 Deprecated.
+ * @access protected
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param int $attachment_id Attachment ID.
+ * @return object|false An Imagify attachment object. False on failure.
+ */
+ protected function get_attachment( $attachment_id ) {
+ _deprecated_function( get_class( $this ) . '::' . __FUNCTION__ . '()', '1.9', '\\Imagify\\ThirdParty\\RegenerateThumbnails\\Main::get_instance()->get_process()' );
+
+ return ! empty( $this->attachments[ $attachment_id ] ) ? $this->attachments[ $attachment_id ] : false;
+ }
+
+ /**
+ * Get the name of the nonce used for the ajax callback.
+ *
+ * @since 1.7.1
+ * @since 1.9 Deprecated.
+ * @access public
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param int $media_id The media ID.
+ * @param string $context The context.
+ * @return string
+ */
+ public static function get_nonce_name( $media_id, $context ) {
+ _deprecated_function( get_called_class() . '::' . __FUNCTION__ . '()', '1.9' );
+
+ return static::ACTION . '-' . $media_id . '-' . $context;
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/deprecated/deprecated.php b/wp-content/plugins/imagify/inc/deprecated/deprecated.php
new file mode 100644
index 00000000..8f427b19
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/deprecated/deprecated.php
@@ -0,0 +1,1665 @@
+schedule_event()' );
+
+ Imagify_Cron_Rating::get_instance()->schedule_event();
+}
+
+/**
+ * Save the user images count to display it later in a notice message to ask him to rate Imagify on WordPress.org.
+ *
+ * @since 1.4.2
+ * @since 1.7 Deprecated.
+ * @deprecated
+ */
+function _do_imagify_rating_cron() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Cron_Rating::get_instance()->do_event()' );
+
+ Imagify_Cron_Rating::get_instance()->do_event();
+}
+
+/**
+ * Adds weekly interval for cron jobs.
+ *
+ * @since 1.6
+ * @since 1.7 Deprecated.
+ * @author Remy Perona
+ * @deprecated
+ *
+ * @param Array $schedules An array of intervals used by cron jobs.
+ * @return Array Updated array of intervals.
+ */
+function imagify_purge_cron_schedule( $schedules ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Cron_Library_Size::get_instance()->maybe_add_recurrence( $schedules )' );
+
+ return Imagify_Cron_Library_Size::get_instance()->do_event( $schedules );
+}
+
+/**
+ * Planning cron task to update weekly the size of the images and the size of images uploaded by month.
+ * If the task is not programmed, it is automatically triggered.
+ *
+ * @since 1.6
+ * @since 1.7 Deprecated.
+ * @author Remy Perona
+ * @deprecated
+ */
+function _imagify_update_library_size_calculations_scheduled() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Cron_Library_Size::get_instance()->schedule_event()' );
+
+ Imagify_Cron_Library_Size::get_instance()->schedule_event();
+}
+
+/**
+ * Cron task to update weekly the size of the images and the size of images uploaded by month.
+ *
+ * @since 1.6
+ * @since 1.7 Deprecated.
+ * @author Remy Perona
+ * @deprecated
+ */
+function _do_imagify_update_library_size_calculations() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Cron_Library_Size::get_instance()->do_event()' );
+
+ Imagify_Cron_Library_Size::get_instance()->do_event();
+}
+
+/**
+ * Set a file permissions using FS_CHMOD_FILE.
+ *
+ * @since 1.2
+ * @since 1.6.5 Use WP Filesystem.
+ * @since 1.7.1 Deprecated.
+ * @deprecated
+ *
+ * @param string $file_path Path to the file.
+ * @return bool True on success, false on failure.
+ */
+function imagify_chmod_file( $file_path ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'imagify_get_filesystem()->chmod_file( $file_path )' );
+
+ return imagify_get_filesystem()->chmod_file( $file_path );
+}
+
+/**
+ * Get a file mime type.
+ *
+ * @since 1.6.9
+ * @since 1.7 Doesn't use exif_imagetype() nor getimagesize() anymore.
+ * @since 1.7.1 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $file_path A file path (prefered) or a filename.
+ * @return string|bool A mime type. False on failure: the test is limited to mime types supported by Imagify.
+ */
+function imagify_get_mime_type_from_file( $file_path ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'imagify_get_filesystem()->get_mime_type( $file_path )' );
+
+ return imagify_get_filesystem()->get_mime_type( $file_path );
+}
+
+/**
+ * Get a file modification date, formated as "mysql". Fallback to current date.
+ *
+ * @since 1.7
+ * @since 1.7.1 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $file_path The file path.
+ * @return string The date.
+ */
+function imagify_get_file_date( $file_path ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'imagify_get_filesystem()->get_date( $file_path )' );
+
+ return imagify_get_filesystem()->get_date( $file_path );
+}
+
+/**
+ * Get a clean value of ABSPATH that can be used in string replacements.
+ *
+ * @since 1.6.8
+ * @since 1.7.1 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @return string The path to WordPress' root folder.
+ */
+function imagify_get_abspath() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'imagify_get_filesystem()->get_abspath()' );
+
+ return imagify_get_filesystem()->get_abspath();
+}
+
+/**
+ * Make an absolute path relative to WordPress' root folder.
+ * Also works for files from registered symlinked plugins.
+ *
+ * @since 1.6.10
+ * @since 1.7 The parameter $base is added.
+ * @since 1.7.1 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $file_path An absolute path.
+ * @param string $base A base path to use instead of ABSPATH.
+ * @return string|bool A relative path. Can return the absolute path or false in case of a failure.
+ */
+function imagify_make_file_path_relative( $file_path, $base = '' ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'imagify_get_filesystem()->make_path_relative( $file_path, $base )' );
+
+ return imagify_get_filesystem()->make_path_relative( $file_path, $base );
+}
+
+/**
+ * Tell if a file is symlinked.
+ *
+ * @since 1.7
+ * @since 1.7.1 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $file_path An absolute path.
+ * @return bool
+ */
+function imagify_file_is_symlinked( $file_path ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'imagify_get_filesystem()->is_symlinked( $file_path )' );
+
+ return imagify_get_filesystem()->is_symlinked( $file_path );
+}
+
+/**
+ * Determine if the Imagify API key is valid.
+ *
+ * @since 1.0
+ * @since 1.7.1 Deprecated.
+ * @deprecated
+ *
+ * @return bool True if the API key is valid.
+ */
+function imagify_valid_key() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'Imagify_Requirements::is_api_key_valid()' );
+
+ return Imagify_Requirements::is_api_key_valid();
+}
+
+/**
+ * Check if external requests are blocked for Imagify.
+ *
+ * @since 1.0
+ * @since 1.7.1 Deprecated.
+ * @deprecated
+ *
+ * @return bool True if Imagify API can't be called.
+ */
+function is_imagify_blocked() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'Imagify_Requirements::is_imagify_blocked()' );
+
+ return Imagify_Requirements::is_imagify_blocked();
+}
+
+/**
+ * Determine if the Imagify API is available by checking the API version.
+ *
+ * @since 1.0
+ * @since 1.7.1 Deprecated.
+ * @deprecated
+ *
+ * @return bool True if the Imagify API is available.
+ */
+function is_imagify_servers_up() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7.1', 'Imagify_Requirements::is_api_up()' );
+
+ return Imagify_Requirements::is_api_up();
+}
+
+/**
+ * Auto-optimize when a new attachment is generated.
+ *
+ * @since 1.0
+ * @since 1.5 Async job.
+ * @since 1.8.4 Deprecated
+ * @see Imagify_Admin_Ajax_Post_Deprecated::imagify_async_optimize_upload_new_media_callback()
+ * @deprecated
+ *
+ * @param array $metadata An array of attachment meta data.
+ * @param int $attachment_id Current attachment ID.
+ * @return array
+ */
+function _imagify_optimize_attachment( $metadata, $attachment_id ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.8.4', 'Imagify_Auto_Optimization::get_instance()->store_upload_ids()' );
+
+ if ( ! Imagify_Requirements::is_api_key_valid() || ! get_imagify_option( 'auto_optimize' ) ) {
+ return $metadata;
+ }
+
+ /**
+ * Allow to prevent automatic optimization for a specific attachment.
+ *
+ * @since 1.6.12
+ * @author Grégory Viguier
+ *
+ * @param bool $optimize True to optimize, false otherwise.
+ * @param int $attachment_id Attachment ID.
+ * @param array $metadata An array of attachment meta data.
+ */
+ $optimize = apply_filters( 'imagify_auto_optimize_attachment', true, $attachment_id, $metadata );
+
+ if ( ! $optimize ) {
+ return $metadata;
+ }
+
+ $context = 'wp';
+ $action = 'imagify_async_optimize_upload_new_media';
+ $_ajax_nonce = wp_create_nonce( 'new_media-' . $attachment_id );
+
+ imagify_do_async_job( compact( 'action', '_ajax_nonce', 'metadata', 'attachment_id', 'context' ) );
+
+ return $metadata;
+}
+
+/**
+ * Optimize an attachment after being resized.
+ *
+ * @since 1.3.6
+ * @since 1.4 Async job.
+ * @since 1.8.4 Deprecated
+ * @deprecated
+ */
+function _imagify_optimize_save_image_editor_file() {
+ _deprecated_function( __FUNCTION__ . '()', '1.8.4' );
+
+ if ( ! isset( $_POST['action'], $_POST['do'], $_POST['postid'] ) || 'image-editor' !== $_POST['action'] || 'open' === $_POST['do'] ) { // WPCS: CSRF ok.
+ return;
+ }
+
+ $attachment_id = absint( $_POST['postid'] );
+
+ if ( ! $attachment_id || ! Imagify_Requirements::is_api_key_valid() ) {
+ return;
+ }
+
+ check_ajax_referer( 'image_editor-' . $attachment_id );
+
+ $attachment = get_imagify_attachment( 'wp', $attachment_id, 'save_image_editor_file' );
+
+ if ( ! $attachment->get_data() ) {
+ return;
+ }
+
+ $body = $_POST;
+ $body['action'] = 'imagify_async_optimize_save_image_editor_file';
+
+ imagify_do_async_job( $body );
+}
+
+
+/**
+ * Display an admin notice informing that the current WP version is lower than the required one.
+ *
+ * @since 1.8.1
+ * @since 1.9 Deprecated
+ * @author Grégory Viguier
+ * @deprecated
+ */
+function imagify_wp_version_notice() {
+ global $wp_version;
+
+ _deprecated_function( __FUNCTION__ . '()', '1.9', 'Imagify_Requirements_Check->print_notice()' );
+
+ if ( is_multisite() ) {
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+
+ $is_active = is_plugin_active_for_network( plugin_basename( IMAGIFY_FILE ) );
+ $capacity = $is_active ? 'manage_network_options' : 'manage_options';
+ } else {
+ $capacity = 'manage_options';
+ }
+
+ if ( ! current_user_can( $capacity ) ) {
+ return;
+ }
+
+ echo '';
+ echo '' . __( 'Notice:', 'imagify' ) . ' ';
+ /* translators: 1 is this plugin name, 2 is the required WP version, 3 is the current WP version. */
+ printf( __( '%1$s requires WordPress %2$s minimum, your website is actually running version %3$s.', 'imagify' ), 'Imagify ', '' . IMAGIFY_WP_MIN . '', '' . $wp_version . '' );
+ echo '
';
+}
+
+/**
+ * Delete the backup file when an attachement is deleted.
+ *
+ * @since 1.0
+ * @since 1.9 Deprecated
+ * @deprecated
+ *
+ * @param int $post_id Attachment ID.
+ */
+function _imagify_delete_backup_file( $post_id ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9', 'imagify_cleanup_after_media_deletion( $post_id )' );
+
+ get_imagify_attachment( 'wp', $post_id, 'delete_attachment' )->delete_backup();
+}
+
+/**
+ * Classes autoloader.
+ *
+ * @since 1.6.12
+ * @since 1.9 Deprecated
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $class Name of the class to include.
+ */
+function imagify_autoload( $class ) {
+ static $strtolower;
+
+ _deprecated_function( __FUNCTION__ . '()', '1.9' );
+
+ if ( ! isset( $strtolower ) ) {
+ $strtolower = function_exists( 'mb_strtolower' ) ? 'mb_strtolower' : 'strtolower';
+ }
+
+ // Generic classes.
+ $classes = array(
+ 'Imagify_Abstract_Attachment' => 1,
+ 'Imagify_Abstract_Background_Process' => 1,
+ 'Imagify_Abstract_Cron' => 1,
+ 'Imagify_Abstract_DB' => 1,
+ 'Imagify_Abstract_Options' => 1,
+ 'Imagify_Admin_Ajax_Post' => 1,
+ 'Imagify_Assets' => 1,
+ 'Imagify_Attachment' => 1,
+ 'Imagify_Auto_Optimization' => 1,
+ 'Imagify_Cron_Library_Size' => 1,
+ 'Imagify_Cron_Rating' => 1,
+ 'Imagify_Cron_Sync_Files' => 1,
+ 'Imagify_Custom_Folders' => 1,
+ 'Imagify_Data' => 1,
+ 'Imagify_DB' => 1,
+ 'Imagify_File_Attachment' => 1,
+ 'Imagify_Files_DB' => 1,
+ 'Imagify_Files_Iterator' => 1,
+ 'Imagify_Files_List_Table' => 1,
+ 'Imagify_Files_Recursive_Iterator' => 1,
+ 'Imagify_Files_Scan' => 1,
+ 'Imagify_Files_Stats' => 1,
+ 'Imagify_Filesystem' => 1,
+ 'Imagify_Folders_DB' => 1,
+ 'Imagify_Notices' => 1,
+ 'Imagify_Options' => 1,
+ 'Imagify_Requirements' => 1,
+ 'Imagify_Settings' => 1,
+ 'Imagify_User' => 1,
+ 'Imagify_Views' => 1,
+ 'Imagify' => 1,
+ );
+
+ if ( isset( $classes[ $class ] ) ) {
+ $class = str_replace( '_', '-', call_user_func( $strtolower, $class ) );
+ include IMAGIFY_PATH . 'inc/classes/class-' . $class . '.php';
+ return;
+ }
+
+ // Third party classes.
+ $classes = array(
+ 'Imagify_AS3CF_Attachment' => 'amazon-s3-and-cloudfront',
+ 'Imagify_AS3CF' => 'amazon-s3-and-cloudfront',
+ 'Imagify_Enable_Media_Replace' => 'enable-media-replace',
+ 'Imagify_Formidable_Pro' => 'formidable-pro',
+ 'Imagify_NGG_Attachment' => 'nextgen-gallery',
+ 'Imagify_NGG_DB' => 'nextgen-gallery',
+ 'Imagify_NGG_Dynamic_Thumbnails_Background_Process' => 'nextgen-gallery',
+ 'Imagify_NGG_Storage' => 'nextgen-gallery',
+ 'Imagify_NGG' => 'nextgen-gallery',
+ 'Imagify_Regenerate_Thumbnails' => 'regenerate-thumbnails',
+ 'Imagify_WP_Retina_2x' => 'wp-retina-2x',
+ 'Imagify_WP_Retina_2x_Core' => 'wp-retina-2x',
+ 'Imagify_WP_Time_Capsule' => 'wp-time-capsule',
+ );
+
+ if ( isset( $classes[ $class ] ) ) {
+ $folder = $classes[ $class ];
+ $class = str_replace( '_', '-', call_user_func( $strtolower, $class ) );
+ include IMAGIFY_PATH . 'inc/3rd-party/' . $folder . '/inc/classes/class-' . $class . '.php';
+ }
+}
+
+/**
+ * Tell if the attachment has the required WP metadata.
+ *
+ * @since 1.6.12
+ * @since 1.7 Also checks that the '_wp_attached_file' meta is valid (not a URL or anything funny).
+ * @since 1.9 Deprecated
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param int $attachment_id The attachment ID.
+ * @return bool
+ */
+function imagify_attachment_has_required_metadata( $attachment_id ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9', '( new Imagify\\Media\\WP( $attachment_id ) )->has_required_media_data() )' );
+
+ $file = get_post_meta( $attachment_id, '_wp_attached_file', true );
+
+ if ( ! $file || preg_match( '@://@', $file ) || preg_match( '@^.:\\\@', $file ) ) {
+ return false;
+ }
+
+ return (bool) wp_get_attachment_metadata( $attachment_id, true );
+}
+
+/**
+ * Get the default Bulk Optimization buffer size.
+ *
+ * @since 1.5.10
+ * @since 1.7 Added $sizes parameter.
+ * @since 1.9 Deprecated
+ * @author Jonathan Buttigieg
+ * @deprecated
+ *
+ * @param int $sizes Number of image sizes per item (attachment).
+ * @return int The buffer size.
+ */
+function get_imagify_bulk_buffer_size( $sizes = false ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9' );
+
+ if ( ! $sizes ) {
+ $sizes = count( get_imagify_thumbnail_sizes() );
+ }
+
+ switch ( true ) {
+ case ( $sizes >= 10 ):
+ return 1;
+
+ case ( $sizes >= 8 ):
+ return 2;
+
+ case ( $sizes >= 6 ):
+ return 3;
+
+ default:
+ return 4;
+ }
+}
+
+/**
+ * Get the Imagify attachment class name depending to a context.
+ *
+ * @since 1.5
+ * @since 1.6.6 $attachment_id and $identifier have been added.
+ * @since 1.9 Deprecated
+ * @author Jonathan Buttigieg
+ * @deprecated
+ *
+ * @param string $context The context to determine the class name.
+ * @param int $attachment_id The attachment ID.
+ * @param string $identifier An identifier.
+ * @return string The Imagify attachment class name.
+ */
+function get_imagify_attachment_class_name( $context, $attachment_id, $identifier ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9', 'imagify_get_optimization_process_class_name( $context )' );
+
+ $context = $context ? $context : 'wp';
+
+ if ( 'wp' !== $context && 'wp' === strtolower( $context ) ) {
+ $context = 'wp';
+ }
+
+ /**
+ * Filter the context used for the optimization.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param string $context The context.
+ * @param int $attachment_id The attachment ID.
+ * @param string $identifier An identifier.
+ */
+ $context = apply_filters( 'imagify_optimize_attachment_context', $context, $attachment_id, $identifier );
+
+ return 'Imagify_' . ( 'wp' !== $context ? $context . '_' : '' ) . 'Attachment';
+}
+
+/**
+ * Get the Imagify attachment instance depending to a context.
+ *
+ * @since 1.6.13
+ * @since 1.9 Deprecated
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $context The context to determine the class name.
+ * @param int $attachment_id The attachment ID.
+ * @param string $identifier An identifier.
+ * @return object The Imagify attachment instance.
+ */
+function get_imagify_attachment( $context, $attachment_id, $identifier ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9', 'imagify_get_optimization_process( $media_id, $context )' );
+
+ $class_name = get_imagify_attachment_class_name( $context, $attachment_id, $identifier );
+ return new $class_name( $attachment_id );
+}
+
+/**
+ * Optimize a file with Imagify.
+ *
+ * @since 1.0
+ * @since 1.9 Deprecated
+ * @deprecated
+ *
+ * @param string $file_path Absolute path to the file.
+ * @param array $args {
+ * Optional. An array of arguments.
+ *
+ * @type bool $backup Force a backup of the original file.
+ * @type int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
+ * @type bool $keep_exif To keep exif data or not.
+ * }
+ * @return array|WP_Error Optimized image data. A WP_Error object on error.
+ */
+function do_imagify( $file_path, $args = array() ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9', '(new Imagify\\Optimization\\File( $file_path ))->optimize( $args )' );
+
+ $args = array_merge( array(
+ 'backup' => get_imagify_option( 'backup' ),
+ 'optimization_level' => get_imagify_option( 'optimization_level' ),
+ 'keep_exif' => get_imagify_option( 'exif' ),
+ 'context' => 'wp',
+ 'resized' => false,
+ 'original_size' => 0,
+ 'backup_path' => null,
+ ), $args );
+
+ /**
+ * Filter the attachment path.
+ *
+ * @since 1.2
+ *
+ * @param string $file_path The attachment path.
+ */
+ $file_path = apply_filters( 'imagify_file_path', $file_path );
+
+ // Check that file path isn't empty.
+ if ( ! $file_path ) {
+ return new WP_Error( 'empty_path', __( 'File path is empty.', 'imagify' ) );
+ }
+
+ // Check if curl is available.
+ if ( ! Imagify_Requirements::supports_curl() ) {
+ return new WP_Error( 'curl', __( 'cURL is not available on the server.', 'imagify' ) );
+ }
+
+ $filesystem = imagify_get_filesystem();
+
+ // Check if imageMagick or GD is available.
+ if ( $filesystem->is_image( $file_path ) && ! Imagify_Requirements::supports_image_editor() ) {
+ return new WP_Error( 'image_editor', sprintf(
+ /* translators: %s is a "More info?" link. */
+ __( 'No php extensions are available to edit images on the server. ImageMagick or GD is required. %s', 'imagify' ),
+ '' . __( 'More info?', 'imagify' ) . ' '
+ ) );
+ }
+
+ // Check if external HTTP requests are blocked.
+ if ( Imagify_Requirements::is_imagify_blocked() ) {
+ return new WP_Error( 'http_block_external', __( 'External HTTP requests are blocked.', 'imagify' ) );
+ }
+
+ // Check if the Imagify servers & the API are accessible.
+ if ( ! Imagify_Requirements::is_api_up() ) {
+ return new WP_Error( 'api_server_down', __( 'Sorry, our servers are temporarily unavailable. Please, try again in a couple of minutes.', 'imagify' ) );
+ }
+
+ // Check that the file exists.
+ if ( ! $filesystem->is_writable( $file_path ) || ! $filesystem->is_file( $file_path ) ) {
+ /* translators: %s is a file path. */
+ return new WP_Error( 'file_not_found', sprintf( __( 'Could not find %s.', 'imagify' ), $filesystem->make_path_relative( $file_path ) ) );
+ }
+
+ // Check that the file directory is writable.
+ if ( ! $filesystem->is_writable( $filesystem->dir_path( $file_path ) ) ) {
+ /* translators: %s is a file path. */
+ return new WP_Error( 'not_writable', sprintf( __( '%s is not writable.', 'imagify' ), $filesystem->make_path_relative( $filesystem->dir_path( $file_path ) ) ) );
+ }
+
+ /**
+ * Fires before to optimize the Image with Imagify.
+ *
+ * @since 1.0
+ *
+ * @param string $file_path Absolute path to the image file.
+ * @param bool $backup Force a backup of the original file.
+ */
+ do_action( 'before_do_imagify', $file_path, $args['backup'] );
+
+ // Create a backup file before sending to optimization (to make sure we can backup the file).
+ $do_backup = $args['backup'] && ! $args['resized'];
+
+ if ( $do_backup ) {
+ $backup_result = imagify_backup_file( $file_path, $args['backup_path'] );
+
+ if ( is_wp_error( $backup_result ) ) {
+ // Stop the process if we can't backup the file.
+ return $backup_result;
+ }
+ }
+
+ // Send image for optimization and fetch the response.
+ $response = upload_imagify_image( array(
+ 'image' => $file_path,
+ 'data' => wp_json_encode( array(
+ 'aggressive' => ( 1 === (int) $args['optimization_level'] ),
+ 'ultra' => ( 2 === (int) $args['optimization_level'] ),
+ 'keep_exif' => $args['keep_exif'],
+ 'context' => $args['context'],
+ 'original_size' => $args['original_size'],
+ ) ),
+ ) );
+
+ // Check status code.
+ if ( is_wp_error( $response ) ) {
+ return new WP_Error( 'api_error', $response->get_error_message() );
+ }
+
+ if ( ! function_exists( 'download_url' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ }
+
+ $temp_file = download_url( $response->image );
+
+ if ( is_wp_error( $temp_file ) ) {
+ return new WP_Error( 'temp_file_not_found', $temp_file->get_error_message() );
+ }
+
+ $filesystem->move( $temp_file, $file_path, true );
+
+ /**
+ * Fires after to optimize the Image with Imagify.
+ *
+ * @since 1.0
+ *
+ * @param string $file_path Absolute path to the image file.
+ * @param bool $backup Force a backup of the original file.
+ */
+ do_action( 'after_do_imagify', $file_path, $args['backup'] );
+
+ return $response;
+}
+
+/**
+ * Backup a file.
+ *
+ * @since 1.6.8
+ * @since 1.9 Deprecated
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $file_path The file path.
+ * @param string $backup_path The backup path. This is useful for NGG for example, who doesn't store the backups in our backup folder.
+ * @return bool|object True on success. False if the backup option is not enabled. A WP_Error object on failure.
+ */
+function imagify_backup_file( $file_path, $backup_path = null ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9', '(new Imagify\\Optimization\\File( $file_path ))->backup( $backup_path )' );
+
+ if ( ! get_imagify_option( 'backup' ) ) {
+ return false;
+ }
+
+ // Make sure the source path is not empty.
+ if ( ! $file_path ) {
+ return new WP_Error( 'empty_path', __( 'The file path is empty.', 'imagify' ) );
+ }
+
+ $filesystem = imagify_get_filesystem();
+
+ // Make sure the filesystem has no errors.
+ if ( ! empty( $filesystem->errors->errors ) ) {
+ return new WP_Error( 'filesystem_error', __( 'Filesystem error.', 'imagify' ), $filesystem->errors );
+ }
+
+ // Make sure the source file exists.
+ if ( ! $filesystem->exists( $file_path ) ) {
+ return new WP_Error( 'source_doesnt_exist', __( 'The file to backup does not exist.', 'imagify' ), array(
+ 'file_path' => $filesystem->make_path_relative( $file_path ),
+ ) );
+ }
+
+ if ( ! isset( $backup_path ) ) {
+ // Make sure the backup directory is writable.
+ if ( ! Imagify_Requirements::attachments_backup_dir_is_writable() ) {
+ return new WP_Error( 'backup_dir_not_writable', __( 'The backup directory is not writable.', 'imagify' ) );
+ }
+
+ $backup_path = get_imagify_attachment_backup_path( $file_path );
+ }
+
+ // Make sure the uploads directory has no errors.
+ if ( ! $backup_path ) {
+ return new WP_Error( 'wp_upload_error', __( 'Error while retrieving the uploads directory path.', 'imagify' ) );
+ }
+
+ // Create sub-directories.
+ $filesystem->make_dir( $filesystem->dir_path( $backup_path ) );
+
+ /**
+ * Allow to overwrite the backup file if it already exists.
+ *
+ * @since 1.6.9
+ * @author Grégory Viguier
+ *
+ * @param bool $overwrite Whether to overwrite the backup file.
+ * @param string $file_path The file path.
+ * @param string $backup_path The backup path.
+ */
+ $overwrite = apply_filters( 'imagify_backup_overwrite_backup', false, $file_path, $backup_path );
+
+ // Copy the file.
+ $filesystem->copy( $file_path, $backup_path, $overwrite, FS_CHMOD_FILE );
+
+ // Make sure the backup copy exists.
+ if ( ! $filesystem->exists( $backup_path ) ) {
+ return new WP_Error( 'backup_doesnt_exist', __( 'The file could not be saved.', 'imagify' ), array(
+ 'file_path' => $filesystem->make_path_relative( $file_path ),
+ 'backup_path' => $filesystem->make_path_relative( $backup_path ),
+ ) );
+ }
+
+ return true;
+}
+
+if ( is_admin() ) :
+
+ /**
+ * Fix the capability for our capacity filter hook
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ * @author Jonathan
+ * @deprecated
+ */
+ function _imagify_correct_capability_for_options_page() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Settings::get_instance()->get_capability()' );
+
+ return Imagify_Settings::get_instance()->get_capability();
+ }
+
+ /**
+ * Tell to WordPress to be confident with our setting, we are clean!
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ * @author Jonathan
+ * @deprecated
+ */
+ function _imagify_register_setting() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Settings::get_instance()->register()' );
+
+ Imagify_Settings::get_instance()->register();
+ }
+
+ /**
+ * Filter specific options before its value is (maybe) serialized and updated.
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ * @author Jonathan
+ * @deprecated
+ *
+ * @param mixed $value The new option value.
+ * @param mixed $old_value The old option value.
+ * @return array The new option value.
+ */
+ function _imagify_pre_update_option( $value, $old_value ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Settings::get_instance()->sanitize_and_validate( $value )' );
+
+ return Imagify_Settings::get_instance()->sanitize_and_validate( $value );
+ }
+
+ /**
+ * If the user clicked the "Save & Go to Bulk Optimizer" button, set a redirection to the bulk optimizer.
+ * We use this hook because it can be triggered even if the option value hasn't changed.
+ *
+ * @since 1.6.8
+ * @since 1.7 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param mixed $value The new, unserialized option value.
+ * @param mixed $old_value The old option value.
+ * @return mixed The option value.
+ */
+ function _imagify_maybe_set_redirection_before_save_options( $value, $old_value ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Settings::get_instance()->maybe_set_redirection( $value, $old_value )' );
+
+ return Imagify_Settings::get_instance()->maybe_set_redirection( $value, $old_value );
+ }
+
+ /**
+ * Used to launch some actions after saving the network options.
+ *
+ * @since 1.6.5
+ * @since 1.7 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $option Name of the network option.
+ * @param mixed $value Current value of the network option.
+ * @param mixed $old_value Old value of the network option.
+ */
+ function _imagify_after_save_network_options( $option, $value, $old_value ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Settings::get_instance()->after_save_network_options( $option, $value, $old_value )' );
+
+ Imagify_Settings::get_instance()->after_save_network_options( $option, $value, $old_value );
+ }
+
+ /**
+ * Used to launch some actions after saving the options.
+ *
+ * @since 1.0
+ * @since 1.5 Used to redirect user to Bulk Optimizer (if requested).
+ * @since 1.6.8 Not used to redirect user to Bulk Optimizer anymore: see _imagify_maybe_set_redirection_before_save_options().
+ * @since 1.7 Deprecated.
+ * @author Jonathan
+ * @deprecated
+ *
+ * @param mixed $old_value The old option value.
+ * @param mixed $value The new option value.
+ */
+ function _imagify_after_save_options( $old_value, $value ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Settings::get_instance()->after_save_options( $old_value, $value )' );
+
+ Imagify_Settings::get_instance()->after_save_options( $old_value, $value );
+ }
+
+ /**
+ * `options.php` do not handle site options. Let's use `admin-post.php` for multisite installations.
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ * @deprecated
+ */
+ function _imagify_update_site_option_on_network() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Settings::get_instance()->update_site_option_on_network()' );
+
+ Imagify_Settings::get_instance()->update_site_option_on_network();
+ }
+
+ /**
+ * Display the plan chooser section.
+ *
+ * @since 1.6
+ * @since 1.7 Deprecated.
+ * @author Geoffrey
+ * @deprecated
+ *
+ * @return string HTML.
+ */
+ function get_imagify_new_to_imagify() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'imagify_get_template( \'part-new-to-imagify\' )' );
+
+ return imagify_get_template( 'part-new-to-imagify' );
+ }
+
+ /**
+ * Get the payment modal HTML.
+ *
+ * @since 1.6
+ * @since 1.6.3 Include discount banners.
+ * @since 1.7 Deprecated.
+ * @author Geoffrey
+ * @deprecated
+ */
+ function imagify_payment_modal() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Views::get_instance()->print_template( \'modal-payment\' )' );
+
+ Imagify_Views::get_instance()->print_template( 'modal-payment' );
+ }
+
+ /**
+ * Print the discount banner used inside Payment Modal.
+ *
+ * @since 1.6.3
+ * @since 1.7 Deprecated.
+ * @author Geoffrey Crofte
+ * @deprecated
+ */
+ function imagify_print_discount_banner() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Views::get_instance()->print_template( \'part-discount-banner\' )' );
+
+ Imagify_Views::get_instance()->print_template( 'part-discount-banner' );
+ }
+
+ /**
+ * Return the formatted price present in pricing tables.
+ *
+ * @since 1.6
+ * @since 1.7 Deprecated.
+ * @author Geoffrey
+ * @deprecated
+ *
+ * @param float $value The price value.
+ * @return string The markuped price.
+ */
+ function get_imagify_price_table_format( $value ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7' );
+
+ $v = explode( '.', (string) $value );
+
+ return '' . $v[0] . ' .' . ( strlen( $v[1] ) === 1 ? $v[1] . '0' : $v[1] ) . ' ';
+ }
+
+ /**
+ * Add submenu in menu "Settings".
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ * @deprecated
+ */
+ function _imagify_settings_menu() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Views::get_instance()->add_network_menus()' );
+
+ Imagify_Views::get_instance()->add_network_menus();
+ }
+
+ /**
+ * Add submenu in menu "Media".
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ * @deprecated
+ */
+ function _imagify_bulk_optimization_menu() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Views::get_instance()->add_site_menus()' );
+
+ Imagify_Views::get_instance()->add_site_menus();
+ }
+
+ /**
+ * The main settings page.
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ * @deprecated
+ */
+ function _imagify_display_options_page() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Views::get_instance()->display_settings_page()' );
+
+ Imagify_Views::get_instance()->display_settings_page();
+ }
+
+ /**
+ * The bulk optimization page.
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ * @deprecated
+ */
+ function _imagify_display_bulk_page() {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Views::get_instance()->display_bulk_page()' );
+
+ Imagify_Views::get_instance()->display_bulk_page();
+ }
+
+ /**
+ * Add link to the plugin configuration pages.
+ *
+ * @since 1.0
+ * @since 1.7 Deprecated.
+ *
+ * @param array $actions An array of action links.
+ * @return array
+ */
+ function _imagify_plugin_action_links( $actions ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.7', 'Imagify_Views::get_instance()->plugin_action_links( $actions )' );
+
+ return Imagify_Views::get_instance()->plugin_action_links( $actions );
+ }
+
+ /**
+ * Get stats data for a specific folder type.
+ *
+ * @since 1.7
+ * @since 1.9 Deprecated
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $context A context.
+ * @return array
+ */
+ function imagify_get_folder_type_data( $context ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9', 'Imagify_Admin_Ajax_Post::get_instance()->get_bulk_instance( $context )->get_context_data()' );
+
+ /**
+ * Get the data.
+ */
+ switch ( $context ) {
+ case 'wp':
+ $total_saving_data = imagify_count_saving_data();
+ $data = array(
+ 'images-optimized' => imagify_count_optimized_attachments(),
+ 'errors' => imagify_count_error_attachments(),
+ 'optimized' => $total_saving_data['optimized_size'],
+ 'original' => $total_saving_data['original_size'],
+ 'errors_url' => get_imagify_admin_url( 'folder-errors', $context ),
+ );
+ break;
+
+ case 'custom-folders':
+ $data = array(
+ 'images-optimized' => Imagify_Files_Stats::count_optimized_files(),
+ 'errors' => Imagify_Files_Stats::count_error_files(),
+ 'optimized' => Imagify_Files_Stats::get_optimized_size(),
+ 'original' => Imagify_Files_Stats::get_original_size(),
+ 'errors_url' => get_imagify_admin_url( 'folder-errors', $context ),
+ );
+ break;
+
+ default:
+ /**
+ * Provide custom folder type data.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $data An array with keys corresponding to cell classes, and values formatted with HTML.
+ * @param string $context A context.
+ */
+ $data = apply_filters( 'imagify_get_folder_type_data', [], $context );
+
+ if ( ! $data || ! is_array( $data ) ) {
+ return [];
+ }
+ }
+
+ /**
+ * Format the data.
+ */
+ /* translators: %s is a formatted number, dont use %d. */
+ $data['images-optimized'] = sprintf( _n( '%s Media File Optimized', '%s Media Files Optimized', $data['images-optimized'], 'imagify' ), '' . number_format_i18n( $data['images-optimized'] ) . ' ' );
+
+ if ( $data['errors'] ) {
+ /* translators: %s is a formatted number, dont use %d. */
+ $data['errors'] = sprintf( _n( '%s Error', '%s Errors', $data['errors'], 'imagify' ), '' . number_format_i18n( $data['errors'] ) . ' ' );
+ $data['errors'] .= ' ' . __( 'View Errors', 'imagify' ) . ' ';
+ } else {
+ $data['errors'] = '';
+ }
+
+ if ( $data['optimized'] ) {
+ $data['optimized'] = '' . __( 'Optimized Filesize', 'imagify' ) . ' ' . imagify_size_format( $data['optimized'], 2 );
+ } else {
+ $data['optimized'] = '';
+ }
+
+ if ( $data['original'] ) {
+ $data['original'] = '' . __( 'Original Filesize', 'imagify' ) . ' ' . imagify_size_format( $data['original'], 2 );
+ } else {
+ $data['original'] = '';
+ }
+
+ unset( $data['errors_url'] );
+
+ return $data;
+ }
+
+ /**
+ * Tell if the current user has the required ability to operate Imagify.
+ *
+ * @since 1.6.11
+ * @since 1.9
+ * @see imagify_get_capacity()
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $describer Capacity describer. See imagify_get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $post_id A post ID.
+ * @return bool
+ */
+ function imagify_current_user_can( $describer = 'manage', $post_id = null ) {
+ static $can_upload;
+
+ _deprecated_function( __FUNCTION__ . '()', '1.9', 'imagify_get_context( $context )->current_user_can( $describer, $media_id )' );
+
+ $post_id = $post_id ? $post_id : null;
+ $capacity = imagify_get_capacity( $describer );
+ $user_can = false;
+
+ if ( 'manage' !== $describer && 'bulk-optimize' !== $describer && 'optimize-file' !== $describer ) {
+ // Describers that are not 'manage', 'bulk-optimize', and 'optimize-file' need an additional test for 'upload_files'.
+ if ( ! isset( $can_upload ) ) {
+ $can_upload = current_user_can( 'upload_files' );
+ }
+
+ if ( $can_upload ) {
+ if ( 'upload_files' === $capacity ) {
+ // We already know it's true.
+ $user_can = true;
+ } else {
+ $user_can = current_user_can( $capacity, $post_id );
+ }
+ }
+ } else {
+ $user_can = current_user_can( $capacity );
+ }
+
+ /**
+ * Filter the current user ability to operate Imagify.
+ *
+ * @since 1.6.11
+ *
+ * @param bool $user_can Tell if the current user has the required ability to operate Imagify.
+ * @param string $capacity The user capacity.
+ * @param string $describer Capacity describer. See imagify_get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $post_id A post ID (a gallery ID for NGG).
+ */
+ return apply_filters( 'imagify_current_user_can', $user_can, $capacity, $describer, $post_id );
+ }
+
+ /**
+ * Get user capacity to operate Imagify.
+ *
+ * @since 1.6.5
+ * @since 1.6.11 Uses a string as describer for the first argument.
+ * @since 1.9 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $describer Capacity describer. Possible values are 'manage', 'bulk-optimize', 'manual-optimize', 'auto-optimize', and 'optimize-file'.
+ * @return string
+ */
+ function imagify_get_capacity( $describer = 'manage' ) {
+ static $edit_attachment_cap;
+
+ _deprecated_function( __FUNCTION__ . '()', '1.9', 'imagify_get_context( $context )->get_capacity( $describer )' );
+
+ // Back compat.
+ if ( ! is_string( $describer ) ) {
+ if ( $describer || ! is_multisite() ) {
+ $describer = 'bulk-optimize';
+ } else {
+ $describer = 'manage';
+ }
+ }
+
+ switch ( $describer ) {
+ case 'manage':
+ $capacity = imagify_is_active_for_network() ? 'manage_network_options' : 'manage_options';
+ break;
+
+ case 'optimize-file':
+ $capacity = is_multisite() ? 'manage_network_options' : 'manage_options';
+ break;
+
+ case 'bulk-optimize':
+ $capacity = 'manage_options';
+ break;
+
+ case 'optimize':
+ case 'restore':
+ // This is a generic capacity: don't use it unless you have no other choices!
+ if ( ! isset( $edit_attachment_cap ) ) {
+ $edit_attachment_cap = get_post_type_object( 'attachment' );
+ $edit_attachment_cap = $edit_attachment_cap ? $edit_attachment_cap->cap->edit_posts : 'edit_posts';
+ }
+
+ $capacity = $edit_attachment_cap;
+ break;
+
+ case 'manual-optimize':
+ case 'manual-restore':
+ // Must be used with an Attachment ID.
+ $capacity = 'edit_post';
+ break;
+
+ case 'auto-optimize':
+ $capacity = 'upload_files';
+ break;
+
+ default:
+ $capacity = $describer;
+ }
+
+ /**
+ * Filter the user capacity used to operate Imagify.
+ *
+ * @since 1.0
+ * @since 1.6.5 Added $force_mono parameter.
+ * @since 1.6.11 Replaced $force_mono by $describer.
+ *
+ * @param string $capacity The user capacity.
+ * @param string $describer Capacity describer. Possible values are 'manage', 'bulk-optimize', 'manual-optimize', 'auto-optimize', and 'optimize-file'.
+ */
+ return apply_filters( 'imagify_capacity', $capacity, $describer );
+ }
+
+ /**
+ * Check for user capacity.
+ *
+ * @since 1.6.10
+ * @since 1.6.11 Uses a capacity describer instead of a capacity itself.
+ * @since 1.9 Deprecated.
+ * @see imagify_get_capacity()
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param string $describer Capacity describer. See imagify_get_capacity() for possible values. Can also be a "real" user capacity.
+ * @param int $post_id A post ID.
+ */
+ function imagify_check_user_capacity( $describer = 'manage', $post_id = null ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9' );
+
+ if ( ! imagify_current_user_can( $describer, $post_id ) ) {
+ imagify_die();
+ }
+ }
+
+ /**
+ * Update the Heartbeat API settings.
+ *
+ * @since 1.4.5
+ * @since 1.9.3 Deprecated.
+ * @deprecated
+ *
+ * @param array $settings Heartbeat API settings.
+ * @return array
+ */
+ function _imagify_heartbeat_settings( $settings ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3' );
+
+ $settings['interval'] = 30;
+ return $settings;
+ }
+
+ /**
+ * Prepare the data that goes back with the Imagifybeat API.
+ *
+ * @since 1.4.5
+ * @since 1.9.3 Deprecated.
+ * @deprecated
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ function _imagify_heartbeat_received( $response, $data ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3', '\\Imagify\\Imagifybeat\\Actions::get_instance()->add_bulk_optimization_stats_to_response()' );
+
+ $heartbeat_id = 'imagify_bulk_data';
+
+ if ( empty( $data[ $heartbeat_id ] ) ) {
+ return $response;
+ }
+
+ $folder_types = array_flip( array_filter( $data[ $heartbeat_id ] ) );
+
+ $response[ $heartbeat_id ] = imagify_get_bulk_stats( $folder_types, array(
+ 'fullset' => true,
+ ) );
+
+ return $response;
+ }
+
+ /**
+ * Prepare the data that goes back with the Imagifybeat API.
+ *
+ * @since 1.7.1
+ * @since 1.9.3 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ function imagify_heartbeat_requirements_received( $response, $data ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3', '\\Imagify\\Imagifybeat\\Actions::get_instance()->add_requirements_to_response()' );
+
+ $heartbeat_id = 'imagify_bulk_requirements';
+
+ if ( empty( $data[ $heartbeat_id ] ) ) {
+ return $response;
+ }
+
+ $response[ $heartbeat_id ] = array(
+ 'curl_missing' => ! Imagify_Requirements::supports_curl(),
+ 'editor_missing' => ! Imagify_Requirements::supports_image_editor(),
+ 'external_http_blocked' => Imagify_Requirements::is_imagify_blocked(),
+ 'api_down' => Imagify_Requirements::is_imagify_blocked() || ! Imagify_Requirements::is_api_up(),
+ 'key_is_valid' => ! Imagify_Requirements::is_imagify_blocked() && Imagify_Requirements::is_api_up() && Imagify_Requirements::is_api_key_valid(),
+ 'is_over_quota' => ! Imagify_Requirements::is_imagify_blocked() && Imagify_Requirements::is_api_up() && Imagify_Requirements::is_api_key_valid() && Imagify_Requirements::is_over_quota(),
+ );
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ * This is used in the bulk optimization page.
+ *
+ * @since 1.9
+ * @since 1.9.3 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ function imagify_heartbeat_bulk_optimization_status_received( $response, $data ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3', '\\Imagify\\Imagifybeat\\Actions::get_instance()->add_bulk_optimization_status_to_response()' );
+
+ $heartbeat_id = 'imagify_bulk_queue';
+
+ if ( empty( $data[ $heartbeat_id ] ) || ! is_array( $data[ $heartbeat_id ] ) ) {
+ return $response;
+ }
+
+ $statuses = [];
+
+ foreach ( $data[ $heartbeat_id ] as $item ) {
+ if ( empty( $statuses[ $item['context'] ] ) ) {
+ $statuses[ $item['context'] ] = [];
+ }
+
+ $statuses[ $item['context'] ][ '_' . $item['mediaID'] ] = 1;
+ }
+
+ $results = imagify_get_modified_optimization_statusses( $statuses );
+
+ if ( ! $results ) {
+ return $response;
+ }
+
+ $response[ $heartbeat_id ] = [];
+
+ // Sanitize received data and grab some other info.
+ foreach ( $results as $context_id => $media_atts ) {
+ $process = imagify_get_optimization_process( $media_atts['media_id'], $media_atts['context'] );
+ $optim_data = $process->get_data();
+
+ if ( $optim_data->is_optimized() ) {
+ // Successfully optimized.
+ $full_size_data = $optim_data->get_size_data();
+ $response[ $heartbeat_id ][] = [
+ 'mediaID' => $media_atts['media_id'],
+ 'context' => $media_atts['context'],
+ 'success' => true,
+ 'status' => 'optimized',
+ // Raw data.
+ 'originalOverallSize' => $full_size_data['original_size'],
+ 'newOverallSize' => $full_size_data['optimized_size'],
+ 'overallSaving' => $full_size_data['original_size'] - $full_size_data['optimized_size'],
+ 'thumbnailsCount' => $optim_data->get_optimized_sizes_count(),
+ // Human readable data.
+ 'originalSizeHuman' => imagify_size_format( $full_size_data['original_size'], 2 ),
+ 'newSizeHuman' => imagify_size_format( $full_size_data['optimized_size'], 2 ),
+ 'overallSavingHuman' => imagify_size_format( $full_size_data['original_size'] - $full_size_data['optimized_size'], 2 ),
+ 'originalOverallSizeHuman' => imagify_size_format( $full_size_data['original_size'], 2 ),
+ 'percentHuman' => $full_size_data['percent'] . '%',
+ ];
+ } elseif ( $optim_data->is_already_optimized() ) {
+ // Already optimized.
+ $response[ $heartbeat_id ][] = [
+ 'mediaID' => $media_atts['media_id'],
+ 'context' => $media_atts['context'],
+ 'success' => true,
+ 'status' => 'already-optimized',
+ ];
+ } else {
+ // Error.
+ $full_size_data = $optim_data->get_size_data();
+ $message = ! empty( $full_size_data['error'] ) ? $full_size_data['error'] : '';
+ $status = 'error';
+
+ if ( 'You\'ve consumed all your data. You have to upgrade your account to continue' === $message ) {
+ $status = 'over-quota';
+ }
+
+ $response[ $heartbeat_id ][] = [
+ 'mediaID' => $media_atts['media_id'],
+ 'context' => $media_atts['context'],
+ 'success' => false,
+ 'status' => $status,
+ 'error' => imagify_translate_api_message( $message ),
+ ];
+ }
+ }
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ * This is used in the settings page.
+ *
+ * @since 1.9
+ * @since 1.9.3 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ function imagify_heartbeat_options_bulk_optimization_status_received( $response, $data ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3', '\\Imagify\\Imagifybeat\\Actions::get_instance()->add_options_optimization_status_to_response()' );
+
+ $heartbeat_id = 'imagify_options_bulk_queue';
+
+ if ( empty( $data[ $heartbeat_id ] ) || ! is_array( $data[ $heartbeat_id ] ) ) {
+ return $response;
+ }
+
+ $statuses = [];
+
+ foreach ( $data[ $heartbeat_id ] as $item ) {
+ if ( empty( $statuses[ $item['context'] ] ) ) {
+ $statuses[ $item['context'] ] = [];
+ }
+
+ $statuses[ $item['context'] ][ '_' . $item['mediaID'] ] = 1;
+ }
+
+ $results = imagify_get_modified_optimization_statusses( $statuses );
+
+ if ( ! $results ) {
+ return $response;
+ }
+
+ $response[ $heartbeat_id ] = [];
+
+ foreach ( $results as $result ) {
+ $response[ $heartbeat_id ][] = [
+ 'mediaID' => $result['media_id'],
+ 'context' => $result['context'],
+ ];
+ }
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ * This is used in the WP Media Library.
+ *
+ * @since 1.9
+ * @since 1.9.3 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ function imagify_heartbeat_optimization_status_received( $response, $data ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3', '\\Imagify\\Imagifybeat\\Actions::get_instance()->add_library_optimization_status_to_response()' );
+
+ $heartbeat_id = get_imagify_localize_script_translations( 'media-modal' );
+ $heartbeat_id = $heartbeat_id['heartbeatId'];
+
+ if ( empty( $data[ $heartbeat_id ] ) || ! is_array( $data[ $heartbeat_id ] ) ) {
+ return $response;
+ }
+
+ $response[ $heartbeat_id ] = imagify_get_modified_optimization_statusses( $data[ $heartbeat_id ] );
+
+ if ( ! $response[ $heartbeat_id ] ) {
+ return $response;
+ }
+
+ // Sanitize received data and grab some other info.
+ foreach ( $response[ $heartbeat_id ] as $context_id => $media_atts ) {
+ $process = imagify_get_optimization_process( $media_atts['media_id'], $media_atts['context'] );
+
+ $response[ $heartbeat_id ][ $context_id ] = get_imagify_media_column_content( $process, false );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ * This is used in the custom folders list (the "Other Media" page).
+ *
+ * @since 1.9
+ * @since 1.9.3 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param array $response The Imagifybeat response.
+ * @param array $data The $_POST data sent.
+ * @return array
+ */
+ function imagify_heartbeat_custom_folders_optimization_status_received( $response, $data ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3', '\\Imagify\\Imagifybeat\\Actions::get_instance()->add_custom_folders_optimization_status_to_response()' );
+
+ $heartbeat_id = get_imagify_localize_script_translations( 'files-list' );
+ $heartbeat_id = $heartbeat_id['heartbeatId'];
+
+ if ( empty( $data[ $heartbeat_id ] ) || ! is_array( $data[ $heartbeat_id ] ) ) {
+ return $response;
+ }
+
+ $response[ $heartbeat_id ] = imagify_get_modified_optimization_statusses( $data[ $heartbeat_id ] );
+
+ if ( ! $response[ $heartbeat_id ] ) {
+ return $response;
+ }
+
+ $admin_ajax_post = Imagify_Admin_Ajax_Post::get_instance();
+ $list_table = new Imagify_Files_List_Table( [
+ 'screen' => 'imagify-files',
+ ] );
+
+ // Sanitize received data and grab some other info.
+ foreach ( $response[ $heartbeat_id ] as $context_id => $media_atts ) {
+ $process = imagify_get_optimization_process( $media_atts['media_id'], $media_atts['context'] );
+
+ $response[ $heartbeat_id ][ $context_id ] = $admin_ajax_post->get_media_columns( $process, $list_table );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Look for media where status has changed, compared to what Imagifybeat sends.
+ *
+ * @since 1.9
+ * @since 1.9.3 Deprecated.
+ * @author Grégory Viguier
+ * @deprecated
+ *
+ * @param array $data The data received.
+ * @return array
+ */
+ function imagify_get_modified_optimization_statusses( $data ) {
+ _deprecated_function( __FUNCTION__ . '()', '1.9.3', '\\Imagify\\Imagifybeat\\Actions::get_instance()->get_modified_optimization_statuses()' );
+
+ if ( ! $data ) {
+ return [];
+ }
+
+ $output = [];
+
+ // Sanitize received data and grab some other info.
+ foreach ( $data as $context => $media_statuses ) {
+ if ( ! $context || ! $media_statuses || ! is_array( $media_statuses ) ) {
+ continue;
+ }
+
+ // Sanitize the IDs: IDs come as strings, prefixed with an undescore character (to prevent JavaScript from screwing everything).
+ $media_ids = array_keys( $media_statuses );
+ $media_ids = array_map( function( $media_id ) {
+ return (int) substr( $media_id, 1 );
+ }, $media_ids );
+ $media_ids = array_filter( $media_ids );
+
+ if ( ! $media_ids ) {
+ continue;
+ }
+
+ // Sanitize the context.
+ $context_instance = imagify_get_context( $context );
+ $context = $context_instance->get_name();
+ $process_class_name = imagify_get_optimization_process_class_name( $context );
+ $transient_name = sprintf( $process_class_name::LOCK_NAME, $context, '%' );
+ $is_network_wide = $context_instance->is_network_wide();
+
+ Imagify_DB::cache_process_locks( $context, $media_ids );
+
+ // Now that everything is cached for this context, we can get the transients without hitting the DB.
+ foreach ( $media_ids as $id ) {
+ $is_locked = (bool) $media_statuses[ '_' . $id ];
+ $option_name = str_replace( '%', $id, $transient_name );
+
+ if ( $is_network_wide ) {
+ $in_db = (bool) get_site_transient( $option_name );
+ } else {
+ $in_db = (bool) get_transient( $option_name );
+ }
+
+ if ( $is_locked === $in_db ) {
+ continue;
+ }
+
+ $output[ $context . '_' . $id ] = [
+ 'media_id' => $id,
+ 'context' => $context,
+ ];
+ }
+ }
+
+ return $output;
+ }
+
+endif;
diff --git a/wp-content/plugins/imagify/inc/functions/admin-stats.php b/wp-content/plugins/imagify/inc/functions/admin-stats.php
new file mode 100644
index 00000000..95f1bb69
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/admin-stats.php
@@ -0,0 +1,792 @@
+get_var( // WPCS: unprepared SQL ok.
+ "
+ SELECT COUNT( p.ID )
+ FROM $wpdb->posts AS p
+ $nodata_join
+ WHERE p.post_mime_type IN ( $mime_types )
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ $nodata_where"
+ );
+
+ if ( $count > imagify_get_unoptimized_attachment_limit() ) {
+ set_transient( 'imagify_large_library', 1 );
+ } elseif ( get_transient( 'imagify_large_library' ) ) {
+ // In case the number is decreasing under our limit.
+ delete_transient( 'imagify_large_library' );
+ }
+
+ return $count;
+}
+
+/**
+ * Count number of optimized attachments with an error.
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ *
+ * @return int The number of attachments.
+ */
+function imagify_count_error_attachments() {
+ global $wpdb;
+ static $count;
+
+ /**
+ * Filter the number of optimized attachments with an error.
+ * 3rd party will be able to override the result.
+ *
+ * @since 1.5
+ *
+ * @param int|bool $pre_count Default is false. Provide an integer.
+ */
+ $pre_count = apply_filters( 'imagify_count_error_attachments', false );
+
+ if ( false !== $pre_count ) {
+ return (int) $pre_count;
+ }
+
+ if ( isset( $count ) ) {
+ return $count;
+ }
+
+ $mime_types = Imagify_DB::get_mime_types();
+ $statuses = Imagify_DB::get_post_statuses();
+ $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause();
+ $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause();
+ $count = (int) $wpdb->get_var( // WPCS: unprepared SQL ok.
+ "
+ SELECT COUNT( DISTINCT p.ID )
+ FROM $wpdb->posts AS p
+ $nodata_join
+ INNER JOIN $wpdb->postmeta AS mt1
+ ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
+ WHERE p.post_mime_type IN ( $mime_types )
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ AND mt1.meta_value = 'error'
+ $nodata_where"
+ );
+
+ return $count;
+}
+
+/**
+ * Count number of optimized attachments (by Imagify or an other tool before).
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ *
+ * @return int The number of attachments.
+ */
+function imagify_count_optimized_attachments() {
+ global $wpdb;
+ static $count;
+
+ /**
+ * Filter the number of optimized attachments.
+ * 3rd party will be able to override the result.
+ *
+ * @since 1.5
+ *
+ * @param int|bool $pre_count Default is false. Provide an integer.
+ */
+ $pre_count = apply_filters( 'imagify_count_optimized_attachments', false );
+
+ if ( false !== $pre_count ) {
+ return (int) $pre_count;
+ }
+
+ if ( isset( $count ) ) {
+ return $count;
+ }
+
+ $mime_types = Imagify_DB::get_mime_types();
+ $statuses = Imagify_DB::get_post_statuses();
+ $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause();
+ $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause();
+ $count = (int) $wpdb->get_var( // WPCS: unprepared SQL ok.
+ "
+ SELECT COUNT( DISTINCT p.ID )
+ FROM $wpdb->posts AS p
+ $nodata_join
+ INNER JOIN $wpdb->postmeta AS mt1
+ ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
+ WHERE p.post_mime_type IN ( $mime_types )
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ AND mt1.meta_value IN ( 'success', 'already_optimized' )
+ $nodata_where"
+ );
+
+ return $count;
+}
+
+/**
+ * Count number of unoptimized attachments.
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ *
+ * @return int The number of attachments.
+ */
+function imagify_count_unoptimized_attachments() {
+ /**
+ * Filter the number of unoptimized attachments.
+ * 3rd party will be able to override the result.
+ *
+ * @since 1.5
+ *
+ * @param int|bool $pre_count Default is false. Provide an integer.
+ */
+ $pre_count = apply_filters( 'imagify_count_unoptimized_attachments', false );
+
+ if ( false !== $pre_count ) {
+ return (int) $pre_count;
+ }
+
+ return imagify_count_attachments() - imagify_count_optimized_attachments() - imagify_count_error_attachments();
+}
+
+/**
+ * Count percent of optimized attachments.
+ *
+ * @since 1.0
+ * @author Jonathan Buttigieg
+ *
+ * @return int The percent of optimized attachments.
+ */
+function imagify_percent_optimized_attachments() {
+ /**
+ * Filter the percent of optimized attachments.
+ * 3rd party will be able to override the result.
+ *
+ * @since 1.5
+ *
+ * @param int|bool $percent Default is false. Provide an integer.
+ */
+ $percent = apply_filters( 'imagify_percent_optimized_attachments', false );
+
+ if ( false !== $percent ) {
+ return (int) $percent;
+ }
+
+ $total_attachments = imagify_count_attachments();
+ $total_optimized_attachments = imagify_count_optimized_attachments();
+
+ if ( ! $total_attachments || ! $total_optimized_attachments ) {
+ return 0;
+ }
+
+ return min( round( 100 * $total_optimized_attachments / $total_attachments ), 100 );
+}
+
+/**
+ * Count percent, original & optimized size of all images optimized by Imagify.
+ *
+ * @since 1.0
+ * @since 1.6.7 Revamped to handle huge libraries.
+ * @author Jonathan Buttigieg
+ *
+ * @param string $key What data to return. Choices are between 'count', 'original_size', 'optimized_size', and 'percent'. If left empty, the whole array is returned.
+ * @return array|int An array containing the optimization data. A single data if $key is provided.
+ */
+function imagify_count_saving_data( $key = '' ) {
+ global $wpdb;
+
+ /**
+ * Filter the query to get all optimized attachments.
+ * 3rd party will be able to override the result.
+ *
+ * @since 1.5
+ * @since 1.6.7 This filter should return an array containing the following keys: 'count', 'original_size', and 'optimized_size'.
+ *
+ * @param bool|array $attachments An array containing the keys ('count', 'original_size', and 'optimized_size'), or an array of attachments (back compat', deprecated), or false.
+ */
+ $attachments = apply_filters( 'imagify_count_saving_data', false );
+
+ $original_size = 0;
+ $optimized_size = 0;
+ $count = 0;
+
+ if ( is_array( $attachments ) ) {
+ /**
+ * Bypass.
+ */
+ if ( isset( $attachments['count'], $attachments['original_size'], $attachments['optimized_size'] ) ) {
+ /**
+ * We have the results we need.
+ */
+ $attachments['percent'] = $attachments['optimized_size'] && $attachments['original_size'] ? ceil( ( ( $attachments['original_size'] - $attachments['optimized_size'] ) / $attachments['original_size'] ) * 100 ) : 0;
+
+ return $attachments;
+ }
+
+ /**
+ * Back compat'.
+ * The following shouldn't be used. Sites with a huge library won't like it.
+ */
+ $attachments = array_map( 'maybe_unserialize', (array) $attachments );
+
+ if ( $attachments ) {
+ foreach ( $attachments as $attachment_data ) {
+ if ( ! $attachment_data ) {
+ continue;
+ }
+
+ ++$count;
+ $original_data = $attachment_data['sizes']['full'];
+
+ // Increment the original sizes.
+ $original_size += $original_data['original_size'] ? $original_data['original_size'] : 0;
+ $optimized_size += $original_data['optimized_size'] ? $original_data['optimized_size'] : 0;
+
+ unset( $attachment_data['sizes']['full'] );
+
+ // Increment the thumbnails sizes.
+ if ( $attachment_data['sizes'] ) {
+ foreach ( $attachment_data['sizes'] as $size_data ) {
+ if ( ! empty( $size_data['success'] ) ) {
+ $original_size += $size_data['original_size'] ? $size_data['original_size'] : 0;
+ $optimized_size += $size_data['optimized_size'] ? $size_data['optimized_size'] : 0;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ /**
+ * Filter the chunk size of the requests fetching the data.
+ * 15,000 seems to be a good balance between memory used, speed, and number of DB hits.
+ *
+ * @param int $limit The maximum number of elements per chunk.
+ */
+ $limit = apply_filters( 'imagify_count_saving_data_limit', 15000 );
+ $limit = absint( $limit );
+
+ $mime_types = Imagify_DB::get_mime_types();
+ $statuses = Imagify_DB::get_post_statuses();
+ $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause();
+ $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause();
+ $attachment_ids = $wpdb->get_col( // WPCS: unprepared SQL ok.
+ "
+ SELECT p.ID
+ FROM $wpdb->posts AS p
+ $nodata_join
+ INNER JOIN $wpdb->postmeta AS mt1
+ ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
+ WHERE p.post_mime_type IN ( $mime_types )
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ AND mt1.meta_value = 'success'
+ $nodata_where
+ ORDER BY CAST( p.ID AS UNSIGNED )"
+ );
+ $wpdb->flush();
+
+ $attachment_ids = array_map( 'absint', array_unique( $attachment_ids ) );
+ $attachment_ids = array_chunk( $attachment_ids, $limit );
+
+ while ( $attachment_ids ) {
+ $limit_ids = array_shift( $attachment_ids );
+ $limit_ids = implode( ',', $limit_ids );
+
+ $attachments = $wpdb->get_col( // WPCS: unprepared SQL ok.
+ "
+ SELECT meta_value
+ FROM $wpdb->postmeta
+ WHERE post_id IN ( $limit_ids )
+ AND meta_key = '_imagify_data'"
+ );
+ $wpdb->flush();
+
+ unset( $limit_ids );
+
+ if ( ! $attachments ) {
+ // Uh?!
+ continue;
+ }
+
+ $attachments = array_map( 'maybe_unserialize', $attachments );
+
+ foreach ( $attachments as $attachment_data ) {
+ if ( ! $attachment_data ) {
+ continue;
+ }
+
+ if ( empty( $attachment_data['sizes']['full']['success'] ) ) {
+ /**
+ * - Case where this attachment has multiple '_imagify_status' metas, and is fetched (in the above query) as a "success" while the '_imagify_data' says otherwise.
+ * - Case where this meta has no "full" entry.
+ * Don't ask how it's possible, I don't know ¯\(°_o)/¯
+ */
+ continue;
+ }
+
+ $original_data = $attachment_data['sizes']['full'];
+
+ ++$count;
+
+ // Increment the original sizes.
+ $original_size += ! empty( $original_data['original_size'] ) ? $original_data['original_size'] : 0;
+ $optimized_size += ! empty( $original_data['optimized_size'] ) ? $original_data['optimized_size'] : 0;
+
+ unset( $attachment_data['sizes']['full'], $original_data );
+
+ // Increment the thumbnails sizes.
+ if ( $attachment_data['sizes'] ) {
+ foreach ( $attachment_data['sizes'] as $size_data ) {
+ if ( ! empty( $size_data['success'] ) ) {
+ $original_size += ! empty( $size_data['original_size'] ) ? $size_data['original_size'] : 0;
+ $optimized_size += ! empty( $size_data['optimized_size'] ) ? $size_data['optimized_size'] : 0;
+ }
+ }
+ }
+
+ unset( $size_data );
+ }
+
+ unset( $attachments, $attachment_data );
+ } // End while().
+ } // End if().
+
+ $data = array(
+ 'count' => $count,
+ 'original_size' => $original_size,
+ 'optimized_size' => $optimized_size,
+ 'percent' => $original_size && $optimized_size ? ceil( ( ( $original_size - $optimized_size ) / $original_size ) * 100 ) : 0,
+ );
+
+ if ( ! empty( $key ) ) {
+ return isset( $data[ $key ] ) ? $data[ $key ] : 0;
+ }
+
+ return $data;
+}
+
+/**
+ * Returns the estimated total size of the images not optimized.
+ *
+ * We estimate the total size of the images in the library by getting the latest 250 images and their thumbnails
+ * add up their filesizes, and doing some maths to get the total size.
+ *
+ * @since 1.6
+ * @author Remy Perona
+ *
+ * @return int The current estimated total size of images not optimized.
+ */
+function imagify_calculate_total_size_images_library() {
+ global $wpdb;
+
+ $mime_types = Imagify_DB::get_mime_types();
+ $statuses = Imagify_DB::get_post_statuses();
+ $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause();
+ $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause();
+ $image_ids = $wpdb->get_col( // WPCS: unprepared SQL ok.
+ "
+ SELECT p.ID
+ FROM $wpdb->posts AS p
+ $nodata_join
+ WHERE p.post_mime_type IN ( $mime_types )
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ $nodata_where
+ LIMIT 250
+ " );
+
+ if ( ! $image_ids ) {
+ return 0;
+ }
+
+ $count_latest_images = count( $image_ids );
+ $count_total_images = imagify_count_attachments();
+
+ return imagify_calculate_total_image_size( $image_ids, $count_latest_images, $count_total_images );
+}
+
+/**
+ * Returns the estimated average size of the images uploaded per month.
+ *
+ * We estimate the average size of the images uploaded in the library per month by getting the latest 250 images and their thumbnails
+ * for the 3 latest months, add up their filesizes, and doing some maths to get the total average size.
+ *
+ * @since 1.6
+ * @since 1.7 Use wpdb instead of WP_Query.
+ * @author Remy Perona
+ *
+ * @return int The current estimated average size of images uploaded per month.
+ */
+function imagify_calculate_average_size_images_per_month() {
+ global $wpdb;
+
+ $mime_types = Imagify_DB::get_mime_types();
+ $statuses = Imagify_DB::get_post_statuses();
+ $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause( "$wpdb->posts.ID" );
+ $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause();
+ $limit = ' LIMIT 0, 250';
+ $query = "
+ SELECT $wpdb->posts.ID
+ FROM $wpdb->posts
+ $nodata_join
+ WHERE $wpdb->posts.post_mime_type IN ( $mime_types )
+ AND $wpdb->posts.post_type = 'attachment'
+ AND $wpdb->posts.post_status IN ( $statuses )
+ $nodata_where
+ %date_query%";
+
+ // Queries per month.
+ $date_query = new WP_Date_Query( array(
+ array(
+ 'before' => 'now',
+ 'after' => '1 month ago',
+ ),
+ ) );
+
+ $partial_images_uploaded_last_month = $wpdb->get_col( str_replace( '%date_query%', $date_query->get_sql(), $query . $limit ) ); // WPCS: unprepared SQL ok.
+
+ $date_query = new WP_Date_Query( array(
+ array(
+ 'before' => '1 month ago',
+ 'after' => '2 months ago',
+ ),
+ ) );
+
+ $partial_images_uploaded_two_months_ago = $wpdb->get_col( str_replace( '%date_query%', $date_query->get_sql(), $query . $limit ) ); // WPCS: unprepared SQL ok.
+
+ $date_query = new WP_Date_Query( array(
+ array(
+ 'before' => '2 month ago',
+ 'after' => '3 months ago',
+ ),
+ ) );
+
+ $partial_images_uploaded_three_months_ago = $wpdb->get_col( str_replace( '%date_query%', $date_query->get_sql(), $query . $limit ) ); // WPCS: unprepared SQL ok.
+
+ // Total for the 3 months.
+ $partial_images_uploaded_id = array_merge( $partial_images_uploaded_last_month, $partial_images_uploaded_two_months_ago, $partial_images_uploaded_three_months_ago );
+
+ if ( ! $partial_images_uploaded_id ) {
+ return 0;
+ }
+
+ // Total for the 3 months, without the "250" limit.
+ $date_query = new WP_Date_Query( array(
+ array(
+ 'before' => 'now',
+ 'after' => '3 month ago',
+ ),
+ ) );
+
+ $images_uploaded_id = $wpdb->get_col( str_replace( '%date_query%', $date_query->get_sql(), $query ) ); // WPCS: unprepared SQL ok.
+
+ if ( ! $images_uploaded_id ) {
+ return 0;
+ }
+
+ // Number of image attachments uploaded for the 3 latest months, limited to 250 per month.
+ $partial_total_images_uploaded = count( $partial_images_uploaded_id );
+ // Total number of image attachments uploaded for the 3 latest months.
+ $total_images_uploaded = count( $images_uploaded_id );
+
+ return imagify_calculate_total_image_size( $partial_images_uploaded_id, $partial_total_images_uploaded, $total_images_uploaded ) / 3;
+}
+
+/**
+ * Returns the estimated total size of images.
+ *
+ * @since 1.6
+ * @author Remy Perona
+ *
+ * @param array $image_ids Array of image IDs.
+ * @param int $partial_total_images The number of image attachments we're doing the calculation with.
+ * @param int $total_images The total number of image attachments.
+ * @return int The estimated total size of images.
+ */
+function imagify_calculate_total_image_size( $image_ids, $partial_total_images, $total_images ) {
+ global $wpdb;
+
+ $image_ids = array_filter( array_map( 'absint', $image_ids ) );
+
+ if ( ! $image_ids ) {
+ return 0;
+ }
+
+ $results = Imagify_DB::get_metas( array(
+ // Get attachments filename.
+ 'filenames' => '_wp_attached_file',
+ // Get attachments data.
+ 'data' => '_wp_attachment_metadata',
+ // Get Imagify data.
+ 'imagify_data' => '_imagify_data',
+ // Get attachments status.
+ 'statuses' => '_imagify_status',
+ ), $image_ids );
+
+ // Number of image attachments we're doing the calculation with. In case array_filter() removed results.
+ $partial_total_images = count( $image_ids );
+ // Total size of unoptimized size.
+ $partial_size_images = 0;
+ // Total number of thumbnails.
+ $partial_total_intermediate_images = 0;
+
+ $filesystem = imagify_get_filesystem();
+ $is_active_for_network = imagify_is_active_for_network();
+ $disallowed_sizes = get_imagify_option( 'disallowed-sizes' );
+
+ foreach ( $image_ids as $i => $image_id ) {
+ $attachment_status = isset( $results['statuses'][ $image_id ] ) ? $results['statuses'][ $image_id ] : false;
+
+ if ( 'success' === $attachment_status ) {
+ /**
+ * The image files have been optimized.
+ */
+ // Original size.
+ $partial_size_images += isset( $results['imagify_data'][ $image_id ]['stats']['original_size'] ) ? $results['imagify_data'][ $image_id ]['stats']['original_size'] : 0;
+ // Number of thumbnails.
+ $partial_total_intermediate_images += count( $results['imagify_data'][ $image_id ]['sizes'] );
+ unset(
+ $image_ids[ $i ],
+ $results['filenames'][ $image_id ],
+ $results['data'][ $image_id ],
+ $results['imagify_data'][ $image_id ],
+ $results['statuses'][ $image_id ]
+ );
+ continue;
+ }
+
+ /**
+ * The image files are not optimized.
+ */
+ // Create an array containing all this attachment files.
+ $files = array(
+ 'full' => get_imagify_attached_file( $results['filenames'][ $image_id ] ),
+ );
+
+ $sizes = isset( $results['data'][ $image_id ]['sizes'] ) ? $results['data'][ $image_id ]['sizes'] : array();
+
+ if ( $sizes && is_array( $sizes ) ) {
+ if ( ! $is_active_for_network ) {
+ $sizes = array_diff_key( $sizes, $disallowed_sizes );
+ }
+
+ if ( $sizes ) {
+ $full_dirname = $filesystem->dir_path( $files['full'] );
+
+ foreach ( $sizes as $size_key => $size_data ) {
+ $files[ $size_key ] = $full_dirname . '/' . $size_data['file'];
+ }
+ }
+ }
+
+ /**
+ * Allow to provide all files size and the number of thumbnails.
+ *
+ * @since 1.6.7
+ * @author Grégory Viguier
+ *
+ * @param bool $size_and_count False by default.
+ * @param int $image_id The attachment ID.
+ * @param array $files An array of file paths with thumbnail sizes as keys.
+ * @param array $image_ids An array of all attachment IDs.
+ * @return bool|array False by default. Provide an array with the keys 'filesize' (containing the total filesize) and 'thumbnails' (containing the number of thumbnails).
+ */
+ $size_and_count = apply_filters( 'imagify_total_attachment_filesize', false, $image_id, $files, $image_ids );
+
+ if ( is_array( $size_and_count ) ) {
+ $partial_size_images += $size_and_count['filesize'];
+ $partial_total_intermediate_images += $size_and_count['thumbnails'];
+ } else {
+ foreach ( $files as $file ) {
+ if ( $filesystem->exists( $file ) ) {
+ $partial_size_images += $filesystem->size( $file );
+ }
+ }
+
+ unset( $files['full'] );
+ $partial_total_intermediate_images += count( $files );
+ }
+
+ unset(
+ $image_ids[ $i ],
+ $results['filenames'][ $image_id ],
+ $results['data'][ $image_id ],
+ $results['imagify_data'][ $image_id ],
+ $results['statuses'][ $image_id ]
+ );
+ } // End foreach().
+
+ // Number of thumbnails per attachment = Number of thumbnails / Number of attachments.
+ $intermediate_images_per_image = $partial_total_intermediate_images / $partial_total_images;
+ /**
+ * Note: Number of attachments ($partial_total_images) === Number of full sizes.
+ * Average image size = Size of the images / ( Number of full sizes + Number of thumbnails ).
+ * Average image size = Size of the images / Number of images.
+ */
+ $average_size_images = $partial_size_images / ( $partial_total_images + $partial_total_intermediate_images );
+ /**
+ * Note: Total number of attachments ($total_images) === Total number of full sizes.
+ * Total images size = Average image size * ( Total number of full sizes + ( Number of thumbnails per attachment * Total number of attachments ) ).
+ * Total images size = Average image size * ( Total number of full sizes + Total number of thumbnails ).
+ */
+ $total_size_images = $average_size_images * ( $total_images + ( $intermediate_images_per_image * $total_images ) );
+
+ return $total_size_images;
+}
+
+/**
+ * Get all generic stats to be used in the bulk optimization page.
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ *
+ * @param array $types The folder types. If a folder type is "library", the context should be suffixed after a pipe character. They are passed as array keys.
+ * @param array $args {
+ * Optional. An array of arguments.
+ *
+ * @type bool $fullset True to return the full set of data. False to return only the main data.
+ * @type bool $formatting Some of the data is returned formatted.
+ * }
+ * @return array
+ */
+function imagify_get_bulk_stats( $types, $args = array() ) {
+ $types = $types && is_array( $types ) ? $types : array();
+ $args = array_merge( array(
+ 'fullset' => false,
+ 'formatting' => true,
+ ), (array) $args );
+
+ $data = array(
+ // Global chart.
+ 'total_attachments' => 0,
+ 'unoptimized_attachments' => 0,
+ 'optimized_attachments' => 0,
+ 'errors_attachments' => 0,
+ // Stats block.
+ 'already_optimized_attachments' => 0,
+ 'original_human' => 0,
+ 'optimized_human' => 0,
+ );
+
+ if ( isset( $types['library|wp'] ) ) {
+ /**
+ * Library.
+ */
+ $saving_data = imagify_count_saving_data();
+
+ // Global chart.
+ $data['total_attachments'] += imagify_count_attachments();
+ $data['unoptimized_attachments'] += imagify_count_unoptimized_attachments();
+ $data['optimized_attachments'] += imagify_count_optimized_attachments();
+ $data['errors_attachments'] += imagify_count_error_attachments();
+ // Stats block.
+ $data['already_optimized_attachments'] += $saving_data['count'];
+ $data['original_human'] += $saving_data['original_size'];
+ $data['optimized_human'] += $saving_data['optimized_size'];
+ }
+
+ if ( isset( $types['custom-folders|custom-folders'] ) ) {
+ /**
+ * Custom folders.
+ */
+ // Global chart.
+ $data['total_attachments'] += Imagify_Files_Stats::count_all_files();
+ $data['unoptimized_attachments'] += Imagify_Files_Stats::count_no_status_files();
+ $data['optimized_attachments'] += Imagify_Files_Stats::count_optimized_files();
+ $data['errors_attachments'] += Imagify_Files_Stats::count_error_files();
+ // Stats block.
+ $data['already_optimized_attachments'] += Imagify_Files_Stats::count_success_files();
+ $data['original_human'] += Imagify_Files_Stats::get_original_size();
+ $data['optimized_human'] += Imagify_Files_Stats::get_optimized_size();
+ }
+
+ /**
+ * Full set of data.
+ */
+ if ( $args['fullset'] ) {
+ // User account.
+ $views = Imagify_Views::get_instance();
+
+ $data['unconsumed_quota'] = $views->get_quota_percent();
+ $data['quota_class'] = $views->get_quota_class();
+ $data['quota_icon'] = $views->get_quota_icon();
+ }
+
+ /**
+ * Filter the generic stats used in the bulk optimization page.
+ *
+ * @since 1.7.1
+ * @author Grégory Viguier
+ *
+ * @param array $data The data.
+ * @param array $types The folder types. They are passed as array keys.
+ * @param array $args {
+ * Optional. An array of arguments.
+ *
+ * @type bool $fullset True to return the full set of data. False to return only the main data.
+ * @type bool $formatting Some of the data is returned formatted.
+ * }
+ */
+ $data = apply_filters( 'imagify_bulk_stats', $data, $types, $args );
+
+ /**
+ * Percentages.
+ */
+ if ( $data['total_attachments'] && $data['optimized_attachments'] ) {
+ $data['optimized_attachments_percent'] = round( 100 * $data['optimized_attachments'] / $data['total_attachments'] );
+ } else {
+ $data['optimized_attachments_percent'] = 0;
+ }
+
+ if ( $data['original_human'] && $data['optimized_human'] ) {
+ $data['optimized_percent'] = ceil( 100 - ( 100 * $data['optimized_human'] / $data['original_human'] ) );
+ } else {
+ $data['optimized_percent'] = 0;
+ }
+
+ /**
+ * Formating.
+ */
+ if ( $args['formatting'] ) {
+ $data['already_optimized_attachments'] = number_format_i18n( $data['already_optimized_attachments'] );
+ $data['original_human'] = imagify_size_format( $data['original_human'], 1 );
+ $data['optimized_human'] = imagify_size_format( $data['optimized_human'], 1 );
+ }
+
+ return $data;
+}
diff --git a/wp-content/plugins/imagify/inc/functions/admin-ui.php b/wp-content/plugins/imagify/inc/functions/admin-ui.php
new file mode 100644
index 00000000..39686751
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/admin-ui.php
@@ -0,0 +1,518 @@
+is_valid() ) {
+ return '';
+ }
+
+ $is_media_page = Imagify_Views::get_instance()->is_media_page();
+ $is_library_page = Imagify_Views::get_instance()->is_wp_library_page();
+ $output = $is_media_page ? '' : '';
+ $output .= '';
+ $output .= '';
+ $output .= '' . __( 'View details', 'imagify' ) . ' ';
+ $output .= ' ';
+ $output .= ' ';
+ $output .= '
';
+ $output .= '';
+
+ // Not in metabox.
+ $output .= $output_before . '' . __( 'Original Filesize:', 'imagify' ) . ' ' . $data->get_original_size() . ' ' . $output_after;
+ }
+
+ $output .= $output_before . '' . __( 'Level:', 'imagify' ) . ' ' . $optimization_level . ' ' . $output_after;
+
+ if ( $media->is_image() ) {
+ $has_webp = $process->has_webp() ? __( 'Yes', 'imagify' ) : __( 'No', 'imagify' );
+ $output .= $output_before . '' . __( 'Webp generated:', 'imagify' ) . ' ' . esc_html( $has_webp ) . ' ' . $output_after;
+
+ $total_optimized_thumbnails = $data->get_optimized_sizes_count();
+
+ if ( $total_optimized_thumbnails ) {
+ $output .= $output_before . '' . __( 'Thumbnails Optimized:', 'imagify' ) . ' ' . $total_optimized_thumbnails . ' ' . $output_after;
+ $output .= $output_before . '' . __( 'Overall Saving:', 'imagify' ) . ' ' . $data->get_overall_saving_percent() . '% ' . $output_after;
+ }
+ }
+
+ // End of list.
+ $output .= $is_media_page ? '' : ' ';
+
+ // Actions section.
+ $output .= $is_media_page ? $output_before : '';
+ $output .= $reoptimize_output_before;
+ $output .= $reoptimize_output;
+
+ if ( $media->has_backup() ) {
+ $url = get_imagify_admin_url( 'restore', [
+ 'attachment_id' => $attachment_id,
+ 'context' => $media->get_context(),
+ ] );
+
+ $output .= Imagify_Views::get_instance()->get_template( 'button/restore', [
+ 'url' => $url,
+ 'atts' => [
+ 'class' => $is_media_page ? '' : null,
+ ],
+ ] );
+
+ if ( ! $is_library_page ) {
+ $output .= ' ';
+ $output .= ' ';
+ $output .= ' ';
+
+ if ( $media->is_image() ) {
+ $dimensions = $media->get_dimensions();
+
+ $output .= ' ';
+ $output .= ' ';
+ }
+ }
+ }
+
+ $output .= $reoptimize_output_after;
+
+ return $output;
+}
+
+/**
+ * Get the error message for a specific attachment.
+ *
+ * @since 1.0
+ * @since 1.9 Function signature changed.
+ * @author Jonathan Buttigieg
+ *
+ * @param ProcessInterface $process The optimization process object.
+ * @return string The output to print.
+ */
+function get_imagify_attachment_error_text( $process ) {
+ if ( ! $process->is_valid() ) {
+ return '';
+ }
+
+ $data = $process->get_data()->get_optimization_data();
+
+ if ( ! isset( $data['sizes']['full']['success'] ) || $data['sizes']['full']['success'] ) {
+ return '';
+ }
+
+ $class = 'button';
+ $media = $process->get_media();
+ $url = get_imagify_admin_url( 'optimize', [
+ 'attachment_id' => $media->get_id(),
+ 'context' => $media->get_context(),
+ ] );
+
+ if ( ! Imagify_Views::get_instance()->is_media_page() ) {
+ $class .= ' button-imagify-optimize';
+ }
+
+ return Imagify_Views::get_instance()->get_template( 'button/retry-optimize', [
+ 'url' => $url,
+ 'error' => $data['sizes']['full']['error'],
+ 'atts' => [
+ 'class' => $class,
+ ],
+ ] );
+}
+
+/**
+ * Get the re-optimize link for a specific attachment.
+ *
+ * @since 1.0
+ * @since 1.9 Function signature changed.
+ * @author Jonathan Buttigieg
+ *
+ * @param ProcessInterface $process The optimization process object.
+ * @return string The output to print.
+ */
+function get_imagify_attachment_reoptimize_link( $process ) {
+ if ( ! $process->is_valid() ) {
+ return '';
+ }
+
+ $data = $process->get_data();
+
+ if ( ! $data->get_optimization_status() ) {
+ // Not optimized yet.
+ return '';
+ }
+
+ // Stop the process if the API key isn't valid.
+ if ( ! Imagify_Requirements::is_api_key_valid() ) {
+ return '';
+ }
+
+ $is_already_optimized = $data->is_already_optimized();
+ $media = $process->get_media();
+ $can_reoptimize = $is_already_optimized || $media->has_backup();
+
+ // Don't display anything if there is no backup or the image has been optimized.
+ if ( ! $can_reoptimize ) {
+ return '';
+ }
+
+ $output = '';
+ $views = Imagify_Views::get_instance();
+ $media_level = $data->get_optimization_level();
+ $data = [];
+ $url_args = [
+ 'attachment_id' => $media->get_id(),
+ 'context' => $media->get_context(),
+ ];
+
+ if ( Imagify_Views::get_instance()->is_media_page() ) {
+ $data['atts'] = [
+ 'class' => '',
+ ];
+ }
+
+ foreach ( [ 2, 1, 0 ] as $level ) {
+ /**
+ * Display a link if:
+ * - the level is lower than the one used to optimize the media,
+ * - or, the level is higher and the media is not already optimized.
+ */
+ if ( $media_level < $level || ( $media_level > $level && ! $is_already_optimized ) ) {
+ $url_args['optimization_level'] = $level;
+ $data['optimization_level'] = $level;
+ $data['url'] = get_imagify_admin_url( 'manual-reoptimize', $url_args );
+
+ $output .= $views->get_template( 'button/re-optimize', $data );
+ $output .= ' ';
+ }
+ }
+
+ return $output;
+}
+
+/**
+ * Get the link to optimize missing thumbnail sizes for a specific attachment.
+ *
+ * @since 1.6.10
+ * @since 1.9 Function signature changed.
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process object.
+ * @return string The output to print.
+ */
+function get_imagify_attachment_optimize_missing_thumbnails_link( $process ) {
+ if ( ! $process->is_valid() ) {
+ return '';
+ }
+
+ $media = $process->get_media();
+
+ if ( ! $media->is_image() || ! Imagify_Requirements::is_api_key_valid() || ! $media->has_backup() ) {
+ return '';
+ }
+
+ $context = $media->get_context();
+
+ /**
+ * Allow to not display the "Optimize missing thumbnails" link.
+ *
+ * @since 1.6.10
+ * @since 1.9 The $attachment object is replaced by a $process object.
+ * @author Grégory Viguier
+ *
+ * @param bool $display True to display the link. False to not display it.
+ * @param ProcessInterface $process The optimization process object.
+ * @param string $context The context.
+ */
+ $display = apply_filters( 'imagify_display_missing_thumbnails_link', true, $process, $context );
+
+ // Stop the process if the filter is false.
+ if ( ! $display ) {
+ return '';
+ }
+
+ $missing_sizes = $process->get_missing_sizes();
+
+ if ( ! $missing_sizes || is_wp_error( $missing_sizes ) ) {
+ return '';
+ }
+
+ $url = get_imagify_admin_url( 'optimize-missing-sizes', [
+ 'attachment_id' => $media->get_id(),
+ 'context' => $context,
+ ] );
+
+ return Imagify_Views::get_instance()->get_template( 'button/optimize-missing-sizes', [
+ 'url' => $url,
+ 'count' => count( $missing_sizes ),
+ ] );
+}
+
+/**
+ * Get the link to generate webp versions if they are missing.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process object.
+ * @return string The output to print.
+ */
+function get_imagify_attachment_generate_webp_versions_link( $process ) {
+ if ( ! $process->is_valid() ) {
+ return '';
+ }
+
+ if ( ! get_imagify_option( 'convert_to_webp' ) ) {
+ return '';
+ }
+
+ $media = $process->get_media();
+
+ if ( ! $media->is_image() || ! Imagify_Requirements::is_api_key_valid() || ! $media->has_backup() ) {
+ return '';
+ }
+
+ $data = $process->get_data();
+
+ if ( ! $data->is_optimized() && ! $data->is_already_optimized() ) {
+ return '';
+ }
+
+ if ( $process->has_webp() ) {
+ return '';
+ }
+
+ $context = $media->get_context();
+
+ /**
+ * Allow to not display the "Generate webp versions" link.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $display True to display the link. False to not display it.
+ * @param ProcessInterface $process The optimization process object.
+ * @param string $context The context.
+ */
+ $display = apply_filters( 'imagify_display_generate_webp_versions_link', true, $process, $context );
+
+ // Stop the process if the filter is false.
+ if ( ! $display ) {
+ return '';
+ }
+
+ $url = get_imagify_admin_url( 'generate-webp-versions', [
+ 'attachment_id' => $media->get_id(),
+ 'context' => $context,
+ ] );
+
+ $output = Imagify_Views::get_instance()->get_template( 'button/generate-webp', [
+ 'url' => $url,
+ ] );
+
+ return $output . ' ';
+}
+
+/**
+ * Get the link to delete webp versions when the status is "already_optimized".
+ *
+ * @since 1.9.6
+ * @author Grégory Viguier
+ *
+ * @param ProcessInterface $process The optimization process object.
+ * @return string The output to print.
+ */
+function get_imagify_attachment_delete_webp_versions_link( $process ) {
+ if ( ! $process->is_valid() ) {
+ return '';
+ }
+
+ $media = $process->get_media();
+ $context = $media->get_context();
+ $media_id = $media->get_id();
+
+ if ( ! imagify_get_context( $context )->current_user_can( 'manual-restore', $media_id ) ) {
+ imagify_die();
+ }
+
+ $data = $process->get_data();
+
+ if ( ! $data->is_already_optimized() || ! $process->has_webp() ) {
+ return '';
+ }
+
+ $class = '';
+ $url = get_imagify_admin_url( 'delete-webp-versions', [
+ 'attachment_id' => $media_id,
+ 'context' => $context,
+ ] );
+
+ if ( ! Imagify_Views::get_instance()->is_media_page() ) {
+ $class .= 'button-imagify-delete-webp';
+ }
+
+ return Imagify_Views::get_instance()->get_template( 'button/delete-webp', [
+ 'url' => $url,
+ 'atts' => [
+ 'class' => $class,
+ ],
+ ] );
+}
+
+/**
+ * Get all data to diplay for a specific media.
+ *
+ * @since 1.2
+ * @since 1.9 Function signature changed.
+ * @author Jonathan Buttigieg
+ *
+ * @param ProcessInterface $process The optimization process object.
+ * @param bool $with_container Set to false to not return the HTML container.
+ * @return string The output to print.
+ */
+function get_imagify_media_column_content( $process, $with_container = true ) {
+ if ( ! $process->is_valid() ) {
+ return __( 'This media is not valid.', 'imagify' );
+ }
+
+ if ( ! $process->current_user_can( 'manual-optimize' ) ) {
+ return __( 'You are not allowed to optimize this file.', 'imagify' );
+ }
+
+ $media = $process->get_media();
+
+ // Check if the media is supported.
+ if ( ! $media->is_supported() ) {
+ return __( 'This media is not supported.', 'imagify' );
+ }
+
+ // Check if the media has the required WP data.
+ if ( ! $media->has_required_media_data() ) {
+ return __( 'This media lacks the required metadata and cannot be optimized.', 'imagify' );
+ }
+
+ $data = $process->get_data();
+
+ // Check if the API key is valid.
+ if ( ! Imagify_Requirements::is_api_key_valid() && ! $data->is_optimized() ) {
+ $output = __( 'Invalid API key', 'imagify' );
+ $output .= ' ';
+ $output .= '' . __( 'Check your Settings', 'imagify' ) . ' ';
+ return $output;
+ }
+
+ $media_id = $media->get_id();
+ $context = $media->get_context();
+ $views = Imagify_Views::get_instance();
+ $is_locked = $process->is_locked();
+
+ if ( $is_locked ) {
+ switch ( $is_locked ) {
+ case 'optimizing':
+ $lock_label = __( 'Optimizing...', 'imagify' );
+ break;
+ case 'restoring':
+ $lock_label = __( 'Restoring...', 'imagify' );
+ break;
+ default:
+ $lock_label = __( 'Processing...', 'imagify' );
+ }
+
+ if ( ! $with_container ) {
+ return $views->get_template( 'button/processing', [ 'label' => $lock_label ] );
+ }
+
+ return $views->get_template( 'container/data-actions', [
+ 'media_id' => $media_id,
+ 'context' => $context,
+ 'content' => $views->get_template( 'button/processing', [ 'label' => $lock_label ] ),
+ ] );
+ }
+
+ // Check if the image was optimized.
+ if ( ! $data->get_optimization_status() ) {
+ $output = Imagify_Views::get_instance()->get_template( 'button/optimize', [
+ 'url' => get_imagify_admin_url( 'manual-optimize', [
+ 'attachment_id' => $media_id,
+ 'context' => $context,
+ ] ),
+ ] );
+
+ if ( $media->has_backup() ) {
+ $output .= ' ';
+ }
+ } else {
+ $output = get_imagify_attachment_optimization_text( $process );
+ }
+
+ if ( ! $with_container ) {
+ return $output;
+ }
+
+ return $views->get_template( 'container/data-actions', [
+ 'media_id' => $media_id,
+ 'context' => $context,
+ 'content' => $output,
+ ] );
+}
diff --git a/wp-content/plugins/imagify/inc/functions/admin.php b/wp-content/plugins/imagify/inc/functions/admin.php
new file mode 100644
index 00000000..27f1e75f
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/admin.php
@@ -0,0 +1,444 @@
+in_admin() ) {
+ return false;
+ }
+
+ switch ( $identifier ) {
+ case 'imagify-settings':
+ // Imagify Settings or Imagify Network Settings.
+ $slug = Imagify_Views::get_instance()->get_settings_page_slug();
+ return 'settings_page_' . $slug === $current_screen->id || $slug . '_page_' . $slug . '-network' === $current_screen->id || 'toplevel_page_' . $slug . '-network' === $current_screen->id;
+
+ case 'imagify-network-settings':
+ // Imagify Network Settings.
+ $slug = Imagify_Views::get_instance()->get_settings_page_slug();
+ return $slug . '_page_' . $slug . '-network' === $current_screen->id || 'toplevel_page_' . $slug . '-network' === $current_screen->id;
+
+ case 'library':
+ // Media Library.
+ return 'upload' === $current_screen->id;
+
+ case 'upload':
+ // Upload New Media.
+ return 'media' === $current_screen->id;
+
+ case 'post':
+ // Edit Post, Page, Attachment, etc.
+ return 'post' === $current_screen->base;
+
+ case 'attachment':
+ case 'post-attachment':
+ // Edit Attachment.
+ return 'post' === $current_screen->base && 'attachment' === $current_screen->id && $post_id && imagify_is_attachment_mime_type_supported( $post_id );
+
+ case 'bulk':
+ case 'bulk-optimization':
+ // Bulk Optimization (any).
+ $slug = Imagify_Views::get_instance()->get_bulk_page_slug();
+ return 'toplevel_page_' . $slug . '-network' === $current_screen->id || 'media_page_' . $slug === $current_screen->id;
+
+ case 'files-bulk-optimization':
+ // Bulk Optimization (custom folders).
+ $slug = Imagify_Views::get_instance()->get_bulk_page_slug();
+ return 'toplevel_page_' . $slug . '-network' === $current_screen->id || 'media_page_' . $slug === $current_screen->id;
+
+ case 'files':
+ case 'files-list':
+ // "Custom folders" files list.
+ $slug = Imagify_Views::get_instance()->get_files_page_slug();
+ return 'imagify_page_' . $slug . '-network' === $current_screen->id || 'media_page_' . $slug === $current_screen->id;
+
+ case 'media-modal':
+ // Media modal.
+ return did_action( 'wp_enqueue_media' ) || doing_filter( 'wp_enqueue_media' );
+
+ default:
+ return $identifier === $current_screen->id;
+ }
+}
+
+/**
+ * Get the URL related to specific admin page or action.
+ *
+ * @since 1.0
+ *
+ * @param string $action An action.
+ * @param array|string $arg An array of arguments. It can contain an attachment ID and/or a context.
+ * @return string The URL of the specific admin page or action.
+ */
+function get_imagify_admin_url( $action = 'settings', $arg = [] ) {
+ if ( is_array( $arg ) ) {
+ $id = isset( $arg['attachment_id'] ) ? $arg['attachment_id'] : 0;
+ $context = isset( $arg['context'] ) ? $arg['context'] : 'wp';
+ $level = isset( $arg['optimization_level'] ) ? $arg['optimization_level'] : '';
+ }
+
+ switch ( $action ) {
+ case 'manual-reoptimize':
+ case 'manual-override-upload': // Deprecated.
+ return wp_nonce_url( admin_url( 'admin-post.php?action=imagify_manual_reoptimize&attachment_id=' . $id . '&optimization_level=' . $level . '&context=' . $context ), 'imagify-manual-reoptimize-' . $id . '-' . $context );
+
+ case 'optimize-missing-sizes':
+ return wp_nonce_url( admin_url( 'admin-post.php?action=imagify_optimize_missing_sizes&attachment_id=' . $id . '&context=' . $context ), 'imagify-optimize-missing-sizes-' . $id . '-' . $context );
+
+ case 'generate-webp-versions':
+ return wp_nonce_url( admin_url( 'admin-post.php?action=imagify_generate_webp_versions&attachment_id=' . $id . '&context=' . $context ), 'imagify-generate-webp-versions-' . $id . '-' . $context );
+
+ case 'delete-webp-versions':
+ return wp_nonce_url( admin_url( 'admin-post.php?action=imagify_delete_webp_versions&attachment_id=' . $id . '&context=' . $context ), 'imagify-delete-webp-versions-' . $id . '-' . $context );
+
+ case 'optimize':
+ case 'manual-upload': // Deprecated.
+ case 'manual-optimize':
+ return wp_nonce_url( admin_url( 'admin-post.php?action=imagify_manual_optimize&attachment_id=' . $id . '&context=' . $context ), 'imagify-optimize-' . $id . '-' . $context );
+
+ case 'restore':
+ case 'restore-upload': // Deprecated.
+ return wp_nonce_url( admin_url( 'admin-post.php?action=imagify_restore&attachment_id=' . $id . '&context=' . $context ), 'imagify-restore-' . $id . '-' . $context );
+
+ case 'optimize-file':
+ case 'restore-file':
+ case 'refresh-file-modified':
+ $action = 'imagify_' . str_replace( '-', '_', $action );
+ return wp_nonce_url( admin_url( 'admin-post.php?action=' . $action . '&id=' . $id ), $action );
+
+ case 'reoptimize-file':
+ $action = 'imagify_' . str_replace( '-', '_', $action );
+ return wp_nonce_url( admin_url( 'admin-post.php?action=' . $action . '&id=' . $id . '&level=' . $level ), $action );
+
+ case 'get-files-tree':
+ return wp_nonce_url( admin_url( 'admin-ajax.php?action=imagify_get_files_tree' ), 'get-files-tree' );
+
+ case 'bulk-optimization':
+ return admin_url( 'upload.php?page=' . Imagify_Views::get_instance()->get_bulk_page_slug() );
+
+ case 'files-bulk-optimization':
+ $page = '?page=' . Imagify_Views::get_instance()->get_bulk_page_slug();
+ return imagify_is_active_for_network() ? network_admin_url( 'admin.php' . $page ) : admin_url( 'upload.php' . $page );
+
+ case 'files-list':
+ $page = '?page=' . Imagify_Views::get_instance()->get_files_page_slug();
+ return imagify_is_active_for_network() ? network_admin_url( 'admin.php' . $page ) : admin_url( 'upload.php' . $page );
+
+ case 'folder-errors':
+ switch ( $arg ) {
+ case 'wp':
+ return add_query_arg( array(
+ 'mode' => 'list',
+ 'imagify-status' => 'errors',
+ ), admin_url( 'upload.php' ) );
+
+ case 'custom-folders':
+ return add_query_arg( array(
+ 'status-filter' => 'errors',
+ ), get_imagify_admin_url( 'files-list' ) );
+ }
+ /**
+ * Provide a URL to a page displaying optimization errors for the given context.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $url The URL.
+ * @param string $arg The context.
+ */
+ return apply_filters( 'imagify_optimization_errors_url', '', $arg );
+
+ case 'dismiss-notice':
+ return wp_nonce_url( admin_url( 'admin-post.php?action=imagify_dismiss_notice¬ice=' . $arg ), Imagify_Notices::DISMISS_NONCE_ACTION );
+
+ default:
+ $page = '?page=' . Imagify_Views::get_instance()->get_settings_page_slug();
+ return imagify_is_active_for_network() ? network_admin_url( 'admin.php' . $page ) : admin_url( 'options-general.php' . $page );
+ }
+}
+
+/**
+ * Get maximal width and height from all thumbnails.
+ *
+ * @since 1.1
+ *
+ * @return array An array containing the max width and height.
+ */
+function get_imagify_max_intermediate_image_size() {
+ $width = 0;
+ $height = 0;
+ $limit = 9999;
+
+ foreach ( get_imagify_thumbnail_sizes() as $_size ) {
+ if ( $_size['width'] > $width && $_size['width'] < $limit ) {
+ $width = $_size['width'];
+ }
+
+ if ( $_size['height'] > $height && $_size['height'] < $limit ) {
+ $height = $_size['height'];
+ }
+ }
+
+ return array(
+ 'width' => $width,
+ 'height' => $height,
+ );
+}
+
+/**
+ * Simple helper to get the WP Rocket's site URL.
+ * The URL is localized and contains some utm_*** parameters.
+ *
+ * @since 1.6.8
+ * @since 1.6.9 Added $path and $query parameters.
+ * @author Grégory Viguier
+ *
+ * @param string $path A path to add to the URL (URI). Not in use yet.
+ * @param array $query An array of query arguments (utm_*).
+ * @return string The URL.
+ */
+function imagify_get_wp_rocket_url( $path = false, $query = array() ) {
+ $wprocket_url = 'https://wp-rocket.me/';
+
+ // Current lang.
+ $lang = imagify_get_current_lang_in( array( 'de', 'es', 'fr', 'it' ) );
+
+ if ( 'en' !== $lang ) {
+ $wprocket_url .= $lang . '/';
+ }
+
+ // URI.
+ $paths = array(
+ 'pricing' => array(
+ 'de' => 'preise',
+ 'en' => 'pricing',
+ 'es' => 'precios',
+ 'fr' => 'offres',
+ 'it' => 'offerte',
+ ),
+ );
+
+ if ( $path ) {
+ $path = trim( $path, '/' );
+
+ if ( isset( $paths[ $path ] ) ) {
+ $wprocket_url .= $paths[ $path ][ $lang ] . '/';
+ } else {
+ $wprocket_url .= $path . '/';
+ }
+ }
+
+ // Query args.
+ $query = array_merge( array(
+ 'utm_source' => 'imagify-coupon',
+ 'utm_medium' => 'plugin',
+ 'utm_campaign' => 'imagify',
+ ), $query );
+
+ return add_query_arg( $query, $wprocket_url );
+}
+
+/**
+ * Check for nonce.
+ *
+ * @since 1.9.11 Return true when nonce is good.
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $action Action nonce.
+ * @param string|bool $query_arg Optional. Key to check for the nonce in `$_REQUEST`. If false, `$_REQUEST` values will be evaluated for '_ajax_nonce', and '_wpnonce' (in that order). Default false.
+ *
+ * @return bool True if the nonce is good; otherwise terminates.
+ */
+function imagify_check_nonce( $action, $query_arg = false ) {
+ if ( ! check_ajax_referer( $action, $query_arg, false ) ) {
+ imagify_die();
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Die Today.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $message A message to display.
+ */
+function imagify_die( $message = null ) {
+ if ( ! isset( $message ) ) {
+ /* translators: This sentense already exists in WordPress. */
+ $message = __( 'Sorry, you are not allowed to do that.', 'imagify' );
+ } elseif ( is_wp_error( $message ) ) {
+ $message = imagify_translate_api_message( $message->get_error_message() );
+ }
+
+ if ( is_array( $message ) ) {
+ if ( ! empty( $message['error'] ) ) {
+ $message['error'] = imagify_translate_api_message( $message['error'] );
+ } elseif ( ! empty( $message['detail'] ) ) {
+ $message['detail'] = imagify_translate_api_message( $message['detail'] );
+ }
+ }
+
+ if ( wp_doing_ajax() ) {
+ wp_send_json_error( $message );
+ }
+
+ if ( is_array( $message ) ) {
+ if ( ! empty( $message['error'] ) ) {
+ $message = $message['error'];
+ } elseif ( ! empty( $message['detail'] ) ) {
+ $message = $message['detail'];
+ } else {
+ $message = reset( $message );
+ }
+ }
+
+ if ( wp_get_referer() ) {
+ $message .= '';
+ $message .= sprintf( '%s ',
+ esc_url( remove_query_arg( 'updated', wp_get_referer() ) ),
+ /* translators: This sentense already exists in WordPress. */
+ __( 'Go back', 'imagify' )
+ );
+ }
+
+ /* translators: %s is the plugin name. */
+ wp_die( $message, sprintf( __( '%s Failure Notice', 'imagify' ), 'Imagify' ), 403 );
+}
+
+/**
+ * Redirect if not an ajax request.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $message A message to display in an admin notice once redirected.
+ * @param array|string $args_or_url An array of query args to add to the redirection URL. If a string, the complete URL.
+ */
+function imagify_maybe_redirect( $message = false, $args_or_url = array() ) {
+ if ( wp_doing_ajax() ) {
+ return;
+ }
+
+ if ( $args_or_url && is_array( $args_or_url ) ) {
+ $redirect = add_query_arg( $args_or_url, wp_get_referer() );
+ } elseif ( $args_or_url && is_string( $args_or_url ) ) {
+ $redirect = $args_or_url;
+ } else {
+ $redirect = wp_get_referer();
+ }
+
+ /**
+ * Filter the URL to redirect to.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ *
+ * @param string $redirect The URL to redirect to.
+ */
+ $redirect = apply_filters( 'imagify_redirect_to', $redirect );
+
+ if ( $message ) {
+ if ( is_multisite() && strpos( $redirect, network_admin_url( '/' ) ) === 0 ) {
+ Imagify_Notices::get_instance()->add_network_temporary_notice( $message );
+ } else {
+ Imagify_Notices::get_instance()->add_site_temporary_notice( $message );
+ }
+ }
+
+ wp_safe_redirect( esc_url_raw( $redirect ) );
+ die();
+}
+
+/**
+ * Get cached Imagify user data.
+ * This is usefull to prevent triggering an HTTP request to our server on every page load, but it can be used only where the data doesn't need to be in real time.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @return object|bool An object on success. False otherwise.
+ */
+function imagify_get_cached_user() {
+ if ( ! Imagify_Requirements::is_api_key_valid() ) {
+ return false;
+ }
+
+ if ( imagify_is_active_for_network() ) {
+ $user = get_site_transient( 'imagify_user' );
+ } else {
+ $user = get_transient( 'imagify_user' );
+ }
+
+ return is_object( $user ) ? $user : false;
+}
+
+/**
+ * Cache Imagify user data for 5 minutes.
+ * Runs every methods to store the results. Also stores formatted data like the quota and the next update date.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @return object|bool An object on success. False otherwise.
+ */
+function imagify_cache_user() {
+ if ( ! Imagify_Requirements::is_api_key_valid() ) {
+ return false;
+ }
+
+ $user = new Imagify_User();
+ $data = (object) get_object_vars( $user );
+ $methods = get_class_methods( $user );
+
+ foreach ( $methods as $method ) {
+ if ( '__construct' !== $method ) {
+ $data->$method = $user->$method();
+ }
+ }
+
+ $data->quota_formatted = imagify_size_format( $user->quota * pow( 1024, 2 ) );
+ $data->next_date_update_formatted = date_i18n( get_option( 'date_format' ), strtotime( $user->next_date_update ) );
+
+ if ( imagify_is_active_for_network() ) {
+ set_site_transient( 'imagify_user', $data, 5 * MINUTE_IN_SECONDS );
+ } else {
+ set_transient( 'imagify_user', $data, 5 * MINUTE_IN_SECONDS );
+ }
+
+ return $data;
+}
+
+/**
+ * Delete cached Imagify user data.
+ *
+ * @since 1.9.5
+ * @author Grégory Viguier
+ */
+function imagify_delete_cached_user() {
+ if ( imagify_is_active_for_network() ) {
+ delete_site_transient( 'imagify_user' );
+ } else {
+ delete_transient( 'imagify_user' );
+ }
+}
diff --git a/wp-content/plugins/imagify/inc/functions/api.php b/wp-content/plugins/imagify/inc/functions/api.php
new file mode 100644
index 00000000..30e852c9
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/api.php
@@ -0,0 +1,273 @@
+create_user( $data );
+}
+
+/**
+ * Update your Imagify account.
+ *
+ * @param string $data All user data.
+ * @return object
+ */
+function update_imagify_user( $data ) {
+ return imagify()->update_user( $data );
+}
+
+/**
+ * Get your Imagify account infos.
+ *
+ * @return object
+ */
+function get_imagify_user() {
+ return imagify()->get_user();
+}
+
+/**
+ * Get the Imagify API version.
+ *
+ * @return object
+ */
+function get_imagify_api_version() {
+ return imagify()->get_api_version();
+}
+
+/**
+ * Check your Imagify API key status.
+ *
+ * @param string $data An API key.
+ * @return bool
+ */
+function get_imagify_status( $data ) {
+ return imagify()->get_status( $data );
+}
+
+/**
+ * Optimize an image by uploading it on Imagify.
+ *
+ * @param array $data All image data.
+ * @return object
+ */
+function fetch_imagify_image( $data ) {
+ return imagify()->fetch_image( $data );
+}
+
+/**
+ * Optimize an image by sharing its URL on Imagify.
+ *
+ * @since 1.6.7 $data['image'] can contain the file path (prefered) or the result of `curl_file_create()`.
+ *
+ * @param array $data All image data.
+ * @return object
+ */
+function upload_imagify_image( $data ) {
+ return imagify()->upload_image( $data );
+}
+
+/**
+ * Get Imagify Plans Prices.
+ *
+ * @since 1.5
+ * @author Geoffrey Crofte
+ *
+ * @return object
+ */
+function get_imagify_plans_prices() {
+ return imagify()->get_plans_prices();
+}
+
+/**
+ * Get Imagify Plans Prices.
+ *
+ * @since 1.5
+ * @author Geoffrey Crofte
+ *
+ * @return object
+ */
+function get_imagify_packs_prices() {
+ return imagify()->get_packs_prices();
+}
+
+/**
+ * Get Imagify All Prices (plan & packs).
+ *
+ * @since 1.5.4
+ * @author Geoffrey Crofte
+ *
+ * @return object
+ */
+function get_imagify_all_prices() {
+ return imagify()->get_all_prices();
+}
+
+/**
+ * Check if Coupon Code exists.
+ *
+ * @since 1.6
+ * @author Geoffrey Crofte
+ *
+ * @param string $coupon the coupon code to check.
+ * @return object
+ */
+function check_imagify_coupon_code( $coupon ) {
+ return imagify()->check_coupon_code( $coupon );
+}
+
+/**
+ * Check if Discount/Promotion is available.
+ *
+ * @since 1.6.3
+ * @author Geoffrey Crofte
+ *
+ * @return object
+ */
+function check_imagify_discount() {
+ return imagify()->check_discount();
+}
+
+/**
+ * Get Maximum image size for free plan.
+ *
+ * @since 1.5.6
+ * @author Remy Perona
+ *
+ * @return string
+ */
+function get_imagify_max_image_size() {
+ $max_image_size = get_transient( 'imagify_max_image_size' );
+
+ if ( false === $max_image_size ) {
+ $max_image_size = imagify()->get_public_info();
+
+ if ( ! is_wp_error( $max_image_size ) ) {
+ $max_image_size = $max_image_size->max_image_size;
+ set_transient( 'imagify_max_image_size', $max_image_size, 6 * HOUR_IN_SECONDS );
+ }
+ }
+
+ return $max_image_size;
+}
+
+/**
+ * Translate a message from our servers.
+ *
+ * @since 1.6.10
+ * @author Grégory Viguier
+ * @see Imagify::curl_http_call()
+ * @see Imagify::handle_response()
+ *
+ * @param string $message The message from the server (in English).
+ * @return string If in our list, the translated message. The original message otherwise.
+ */
+function imagify_translate_api_message( $message ) {
+ if ( ! $message ) {
+ return imagify_translate_api_message( 'Unknown error occurred' );
+ }
+
+ if ( is_wp_error( $message ) ) {
+ if ( $message->errors ) {
+ foreach ( (array) $message->errors as $code => $messages ) {
+ if ( $messages ) {
+ $message->errors[ $code ] = array_map( 'imagify_translate_api_message', (array) $messages );
+ }
+ }
+ }
+
+ return $message;
+ }
+
+ if ( is_object( $message ) && ! empty( $message->detail ) ) {
+ $message->detail = imagify_translate_api_message( $message->detail );
+ }
+
+ if ( ! is_string( $message ) ) {
+ return $message;
+ }
+
+ $trim_message = trim( $message, '. ' );
+
+ $messages = array(
+ // Local messages from Imagify::curl_http_call() and Imagify::handle_response().
+ 'Could not initialize a new cURL handle' => __( 'Could not initialize a new cURL handle.', 'imagify' ),
+ 'Unknown error occurred' => __( 'Unknown error occurred.', 'imagify' ),
+ 'Your image is too big to be uploaded on our server' => __( 'Your file is too big to be uploaded on our server.', 'imagify' ),
+ 'Our server returned an invalid response' => __( 'Our server returned an invalid response.', 'imagify' ),
+ 'cURL isn\'t installed on the server' => __( 'cURL is not available on the server.', 'imagify' ),
+ // API messages.
+ 'Authentification not provided' => __( 'Authentication not provided.', 'imagify' ),
+ 'Cannot create client token' => __( 'Cannot create client token.', 'imagify' ),
+ 'Confirm your account to continue optimizing image' => __( 'Confirm your account to continue optimizing files.', 'imagify' ),
+ 'Coupon doesn\'t exist' => __( 'Coupon does not exist.', 'imagify' ),
+ 'Email field shouldn\'t be empty' => __( 'Email field should not be empty.', 'imagify' ),
+ 'Email or Password field shouldn\'t be empty' => __( 'This account already exists.', 'imagify' ),
+ 'Error uploading to data Storage' => __( 'Error uploading to Data Storage.', 'imagify' ),
+ 'Not able to connect to Data Storage API to get the token' => __( 'Unable to connect to Data Storage API to get the token.', 'imagify' ),
+ 'Not able to connect to Data Storage API' => __( 'Unable to connect to Data Storage API.', 'imagify' ),
+ 'Not able to retrieve the token from DataStorage API' => __( 'Unable to retrieve the token from Data Storage API.', 'imagify' ),
+ 'This email is already registered, you should try another email' => __( 'This email is already registered, you should try another email.', 'imagify' ),
+ 'This user doesn\'t exit' => __( 'This user does not exist.', 'imagify' ),
+ 'Too many request, be patient' => __( 'Too many requests, please be patient.', 'imagify' ),
+ 'Unable to regenerate access token' => __( 'Unable to regenerate access token.', 'imagify' ),
+ 'User not valid' => __( 'User not valid.', 'imagify' ),
+ 'WELL DONE. This image is already compressed, no further compression required' => __( 'WELL DONE. This media file is already optimized, no further optimization is required.', 'imagify' ),
+ 'You are not authorized to perform this action' => __( 'You are not authorized to perform this action.', 'imagify' ),
+ 'You\'ve consumed all your data. You have to upgrade your account to continue' => __( 'You have consumed all your data. You have to upgrade your account to continue.', 'imagify' ),
+ 'Invalid token' => __( 'Invalid API key', 'imagify' ),
+ 'Upload a valid image. The file you uploaded was either not an image or a corrupted image' => __( 'Invalid or corrupted file.', 'imagify' ),
+ );
+
+ if ( isset( $messages[ $trim_message ] ) ) {
+ return $messages[ $trim_message ];
+ }
+
+ // Local message.
+ if ( preg_match( '@^(?:Unknown|An) error occurred \((.+)\)$@', $trim_message, $matches ) ) {
+ /* translators: %s is an error message. */
+ return sprintf( __( 'An error occurred (%s).', 'imagify' ), esc_html( wp_strip_all_tags( $matches[1] ) ) );
+ }
+
+ // Local message.
+ if ( preg_match( '@^Our server returned an error \((.+)\)$@', $trim_message, $matches ) ) {
+ /* translators: %s is an error message. */
+ return sprintf( __( 'Our server returned an error (%s).', 'imagify' ), esc_html( wp_strip_all_tags( $matches[1] ) ) );
+ }
+
+ // API message.
+ if ( preg_match( '@^Custom one time plan starts from (\d+) MB$@', $trim_message, $matches ) ) {
+ /* translators: %s is a formatted number, dont use %d. */
+ return sprintf( __( 'Custom One Time plan starts from %s MB.', 'imagify' ), number_format_i18n( (int) $matches[1] ) );
+ }
+
+ // API message.
+ if ( preg_match( '@^(.*) is not a valid extension$@', $trim_message, $matches ) ) {
+ /* translators: %s is a file extension. */
+ return sprintf( __( '%s is not a valid extension.', 'imagify' ), sanitize_text_field( $matches[1] ) );
+ }
+
+ // API message.
+ if ( preg_match( '@^Request was throttled\. Expected available in ([\d.]+) second$@', $trim_message, $matches ) ) {
+ /* translators: %s is a float number. */
+ return sprintf( _n( 'Request was throttled. Expected available in %s second.', 'Request was throttled. Expected available in %s seconds.', (int) $matches[1], 'imagify' ), sanitize_text_field( $matches[1] ) );
+ }
+
+ return $message;
+}
diff --git a/wp-content/plugins/imagify/inc/functions/attachments.php b/wp-content/plugins/imagify/inc/functions/attachments.php
new file mode 100644
index 00000000..b84b858a
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/attachments.php
@@ -0,0 +1,371 @@
+ 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ );
+ }
+
+ if ( 'image' !== $type ) {
+ $mimes['pdf'] = 'application/pdf';
+ }
+
+ return $mimes;
+}
+
+/**
+ * Tell if an attachment has a supported mime type.
+ * Was previously Imagify_AS3CF::is_mime_type_supported() since 1.6.6.
+ *
+ * @since 1.6.8
+ * @author Grégory Viguier
+ *
+ * @param int $attachment_id The attachment ID.
+ * @return bool
+ */
+function imagify_is_attachment_mime_type_supported( $attachment_id ) {
+ static $is = array( false );
+
+ $attachment_id = absint( $attachment_id );
+
+ if ( isset( $is[ $attachment_id ] ) ) {
+ return $is[ $attachment_id ];
+ }
+
+ $mime_types = imagify_get_mime_types();
+ $mime_types = array_flip( $mime_types );
+ $mime_type = (string) get_post_mime_type( $attachment_id );
+
+ $is[ $attachment_id ] = isset( $mime_types[ $mime_type ] );
+
+ return $is[ $attachment_id ];
+}
+
+/**
+ * Get post statuses related to attachments.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @return array
+ */
+function imagify_get_post_statuses() {
+ static $statuses;
+
+ if ( isset( $statuses ) ) {
+ return $statuses;
+ }
+
+ $statuses = array(
+ 'inherit' => 'inherit',
+ 'private' => 'private',
+ );
+
+ $custom_statuses = get_post_stati( array( 'public' => true ) );
+ unset( $custom_statuses['publish'] );
+
+ if ( $custom_statuses ) {
+ $statuses = array_merge( $statuses, $custom_statuses );
+ }
+
+ /**
+ * Filter the post statuses Imagify is allowed to optimize.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $statuses An array of post statuses. Kays and values are set.
+ */
+ $statuses = apply_filters( 'imagify_post_statuses', $statuses );
+
+ return $statuses;
+}
+
+/**
+ * Tell if the site has attachments (only the ones Imagify would optimize) without the required WP metadata.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+function imagify_has_attachments_without_required_metadata() {
+ global $wpdb;
+ static $has;
+
+ if ( isset( $has ) ) {
+ return $has;
+ }
+
+ $mime_types = Imagify_DB::get_mime_types();
+ $statuses = Imagify_DB::get_post_statuses();
+ $nodata_join = Imagify_DB::get_required_wp_metadata_join_clause( 'p.ID', false, false );
+ $nodata_where = Imagify_DB::get_required_wp_metadata_where_clause( array(
+ 'matching' => false,
+ 'test' => false,
+ ) );
+ $has = (bool) $wpdb->get_var( // WPCS: unprepared SQL ok.
+ "
+ SELECT p.ID
+ FROM $wpdb->posts AS p
+ $nodata_join
+ WHERE p.post_mime_type IN ( $mime_types )
+ AND p.post_type = 'attachment'
+ AND p.post_status IN ( $statuses )
+ $nodata_where
+ LIMIT 1"
+ );
+
+ return $has;
+}
+
+/**
+ * Get the path to the backups directory.
+ *
+ * @since 1.6.8
+ * @author Grégory Viguier
+ *
+ * @param bool $bypass_error True to return the path even if there is an error. This is used when we want to display this path in a message for example.
+ * @return string|bool Path to the backups directory. False on failure.
+ */
+function get_imagify_backup_dir_path( $bypass_error = false ) {
+ static $backup_dir;
+
+ if ( isset( $backup_dir ) ) {
+ return $backup_dir;
+ }
+
+ $upload_basedir = get_imagify_upload_basedir( $bypass_error );
+
+ if ( ! $upload_basedir ) {
+ return false;
+ }
+
+ $backup_dir = $upload_basedir . 'backup/';
+
+ /**
+ * Filter the backup directory path.
+ *
+ * @since 1.0
+ *
+ * @param string $backup_dir The backup directory path.
+ */
+ $backup_dir = apply_filters( 'imagify_backup_directory', $backup_dir );
+ $backup_dir = imagify_get_filesystem()->normalize_dir_path( $backup_dir );
+
+ return $backup_dir;
+}
+
+/**
+ * Tell if the folder containing the backups is writable.
+ *
+ * @since 1.6.8
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+function imagify_backup_dir_is_writable() {
+ return imagify_get_filesystem()->make_dir( get_imagify_backup_dir_path() );
+}
+
+/**
+ * Get the backup path of a specific attachement.
+ *
+ * @since 1.0
+ *
+ * @param string $file_path The file path.
+ * @return string|bool The backup path. False on failure.
+ */
+function get_imagify_attachment_backup_path( $file_path ) {
+ $file_path = wp_normalize_path( (string) $file_path );
+ $upload_basedir = get_imagify_upload_basedir();
+ $backup_dir = get_imagify_backup_dir_path();
+
+ if ( ! $file_path || ! $upload_basedir ) {
+ return false;
+ }
+
+ return preg_replace( '@^' . preg_quote( $upload_basedir, '@' ) . '@', $backup_dir, $file_path );
+}
+
+/**
+ * Retrieve file path for an attachment based on filename.
+ *
+ * @since 1.4.5
+ *
+ * @param int $file_path The file path.
+ * @return string|false The file path to where the attached file should be, false otherwise.
+ */
+function get_imagify_attached_file( $file_path ) {
+ $file_path = wp_normalize_path( (string) $file_path );
+ $upload_basedir = get_imagify_upload_basedir();
+
+ if ( ! $file_path || ! $upload_basedir ) {
+ return false;
+ }
+
+ // The file path is absolute.
+ if ( strpos( $file_path, '/' ) === 0 || preg_match( '|^.:\\\|', $file_path ) ) {
+ return false;
+ }
+
+ // Prepend upload dir.
+ return $upload_basedir . $file_path;
+}
+
+/**
+ * Retrieve the URL for an attachment based on file path.
+ *
+ * @since 1.4.5
+ *
+ * @param string $file_path A relative or absolute file path.
+ * @return string|bool File URL, otherwise false.
+ */
+function get_imagify_attachment_url( $file_path ) {
+ $file_path = wp_normalize_path( (string) $file_path );
+ $upload_basedir = get_imagify_upload_basedir();
+
+ if ( ! $file_path || ! $upload_basedir ) {
+ return false;
+ }
+
+ $upload_baseurl = get_imagify_upload_baseurl();
+
+ // Check that the upload base exists in the (absolute) file location.
+ if ( 0 === strpos( $file_path, $upload_basedir ) ) {
+ // Replace file location with url location.
+ return preg_replace( '@^' . preg_quote( $upload_basedir, '@' ) . '@', $upload_baseurl, $file_path );
+ }
+
+ if ( false !== strpos( '/' . $file_path, '/wp-content/uploads/' ) ) {
+ // Get the directory name relative to the basedir (back compat for pre-2.7 uploads).
+ return trailingslashit( $upload_baseurl . _wp_get_attachment_relative_path( $file_path ) ) . imagify_get_filesystem()->file_name( $file_path );
+ }
+
+ // It's a newly-uploaded file, therefore $file is relative to the basedir.
+ return $upload_baseurl . $file_path;
+}
+
+/**
+ * Get size information for all currently registered thumbnail sizes.
+ *
+ * @since 1.5.10
+ * @since 1.6.10 For consistency, revamped the function like WP does with wp_generate_attachment_metadata().
+ * Removed the filter, added crop value to each size.
+ * @author Grégory Viguier
+ *
+ * @return array {
+ * Data for the currently registered thumbnail sizes.
+ * Size names are used as array keys.
+ *
+ * @type int $width The image width.
+ * @type int $height The image height.
+ * @type bool $crop True to crop, false to resize.
+ * @type string $name The size name.
+ * }
+ */
+function get_imagify_thumbnail_sizes() {
+ // All image size names.
+ $intermediate_image_sizes = get_intermediate_image_sizes();
+ $intermediate_image_sizes = array_flip( $intermediate_image_sizes );
+ // Additional image size attributes.
+ $additional_image_sizes = wp_get_additional_image_sizes();
+
+ // Create the full array with sizes and crop info.
+ foreach ( $intermediate_image_sizes as $size_name => $s ) {
+ $intermediate_image_sizes[ $size_name ] = array(
+ 'width' => '',
+ 'height' => '',
+ 'crop' => false,
+ 'name' => $size_name,
+ );
+
+ if ( isset( $additional_image_sizes[ $size_name ]['width'] ) ) {
+ // For theme-added sizes.
+ $intermediate_image_sizes[ $size_name ]['width'] = (int) $additional_image_sizes[ $size_name ]['width'];
+ } else {
+ // For default sizes set in options.
+ $intermediate_image_sizes[ $size_name ]['width'] = (int) get_option( "{$size_name}_size_w" );
+ }
+
+ if ( isset( $additional_image_sizes[ $size_name ]['height'] ) ) {
+ // For theme-added sizes.
+ $intermediate_image_sizes[ $size_name ]['height'] = (int) $additional_image_sizes[ $size_name ]['height'];
+ } else {
+ // For default sizes set in options.
+ $intermediate_image_sizes[ $size_name ]['height'] = (int) get_option( "{$size_name}_size_h" );
+ }
+
+ if ( isset( $additional_image_sizes[ $size_name ]['crop'] ) ) {
+ // For theme-added sizes.
+ $intermediate_image_sizes[ $size_name ]['crop'] = (int) $additional_image_sizes[ $size_name ]['crop'];
+ } else {
+ // For default sizes set in options.
+ $intermediate_image_sizes[ $size_name ]['crop'] = (int) get_option( "{$size_name}_crop" );
+ }
+ }
+
+ return $intermediate_image_sizes;
+}
+
+/**
+ * A simple helper to get the upload basedir.
+ *
+ * @since 1.6.7
+ * @since 1.6.8 Added the $bypass_error parameter.
+ * @author Grégory Viguier
+ *
+ * @param bool $bypass_error True to return the path even if there is an error. This is used when we want to display this path in a message for example.
+ * @return string|bool The path. False on failure.
+ */
+function get_imagify_upload_basedir( $bypass_error = false ) {
+ return imagify_get_filesystem()->get_upload_basedir( $bypass_error );
+}
+
+/**
+ * A simple helper to get the upload baseurl.
+ *
+ * @since 1.6.7
+ * @author Grégory Viguier
+ *
+ * @return string|bool The URL. False on failure.
+ */
+function get_imagify_upload_baseurl() {
+ return imagify_get_filesystem()->get_upload_baseurl();
+}
+
+/**
+ * Get the maximal number of unoptimized attachments to fetch.
+ *
+ * @since 1.6.14
+ * @author Grégory Viguier
+ *
+ * @return int
+ */
+function imagify_get_unoptimized_attachment_limit() {
+ /**
+ * Filter the unoptimized attachments limit query.
+ *
+ * @since 1.4.4
+ *
+ * @param int $limit The limit (-1 for unlimited).
+ */
+ $limit = (int) apply_filters( 'imagify_unoptimized_attachment_limit', 10000 );
+
+ return -1 === $limit ? PHP_INT_MAX : abs( $limit );
+}
diff --git a/wp-content/plugins/imagify/inc/functions/common.php b/wp-content/plugins/imagify/inc/functions/common.php
new file mode 100644
index 00000000..277271f6
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/common.php
@@ -0,0 +1,628 @@
+can_operate() || ! Imagify_Files_DB::get_instance()->can_operate() ) {
+ $can = false;
+ return $can;
+ }
+
+ // Check for user capacity.
+ $can = imagify_get_context( 'custom-folders' )->current_user_can( 'optimize' );
+
+ return $can;
+}
+
+/**
+ * Simple helper to get some external URLs, like to the documentation.
+ *
+ * @since 1.6.12
+ * @author Grégory Viguier
+ *
+ * @param string $target What we want.
+ * @param array $query_args An array of query arguments.
+ * @return string The URL.
+ */
+function imagify_get_external_url( $target, $query_args = array() ) {
+ $site_url = 'https://imagify.io/';
+ $app_url = 'https://app.imagify.io/#/';
+
+ switch ( $target ) {
+ case 'plugin':
+ /* translators: Plugin URI of the plugin/theme */
+ $url = __( 'https://wordpress.org/plugins/imagify/', 'imagify' );
+ break;
+
+ case 'rate':
+ $url = 'https://wordpress.org/support/view/plugin-reviews/imagify?rate=5#postform';
+ break;
+
+ case 'share-twitter':
+ $url = rawurlencode( imagify_get_external_url( 'plugin' ) );
+ $url = 'https://twitter.com/intent/tweet?source=webclient&original_referer=' . $url . '&url=' . $url . '&related=imagify&hastags=performance,web,wordpress';
+ break;
+
+ case 'share-facebook':
+ $url = rawurlencode( imagify_get_external_url( 'plugin' ) );
+ $url = 'https://www.facebook.com/sharer/sharer.php?u=' . $url;
+ break;
+
+ case 'exif':
+ /* translators: URL to a Wikipedia page explaining what EXIF means. */
+ $url = __( 'https://en.wikipedia.org/wiki/Exchangeable_image_file_format', 'imagify' );
+ break;
+
+ case 'contact':
+ $lang = imagify_get_current_lang_in( 'fr' );
+ $paths = array(
+ 'en' => 'contact',
+ 'fr' => 'fr/contact',
+ );
+
+ $url = $site_url . $paths[ $lang ] . '/';
+ break;
+
+ case 'documentation':
+ $url = $site_url . 'documentation/';
+ break;
+
+ case 'documentation-imagick-gd':
+ $url = $site_url . 'documentation/solve-imagemagick-gd-required/';
+ break;
+
+ case 'register':
+ $partner = imagify_get_partner();
+
+ if ( $partner ) {
+ $query_args['partner'] = $partner;
+ }
+
+ $url = $app_url . 'register';
+ break;
+
+ case 'subscription':
+ $url = $app_url . 'subscription';
+ break;
+
+ case 'get-api-key':
+ $url = $app_url . 'api';
+ break;
+
+ case 'payment':
+ // Don't remove the trailing slash.
+ $url = $app_url . 'plugin/';
+ break;
+
+ default:
+ return '';
+ }
+
+ if ( $query_args ) {
+ $url = add_query_arg( $query_args, $url );
+ }
+
+ return $url;
+}
+
+/**
+ * Get the current lang ('fr', 'en', 'de'...), limited to a given list.
+ *
+ * @since 1.6.14
+ * @author Grégory Viguier
+ *
+ * @param array $langs An array of langs, like array( 'de', 'es', 'fr', 'it' ).
+ * @return string The current lang. Default is 'en'.
+ */
+function imagify_get_current_lang_in( $langs ) {
+ static $locale;
+
+ if ( ! isset( $locale ) ) {
+ $locale = imagify_get_locale();
+ $locale = explode( '_', strtolower( $locale . '_' ) ); // Trailing underscore is to make sure $locale[1] is set.
+ }
+
+ foreach ( (array) $langs as $lang ) {
+ if ( $lang === $locale[0] || $lang === $locale[1] ) {
+ return $lang;
+ }
+ }
+
+ return 'en';
+}
+
+/**
+ * Get the current locale.
+ *
+ * @since 1.6.14
+ * @author Grégory Viguier
+ *
+ * @return string The current locale.
+ */
+function imagify_get_locale() {
+ $locale = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
+ /**
+ * Filter the locale used by Imagify.
+ *
+ * @since 1.6.14
+ * @author Grégory Viguier
+ *
+ * @param string $locale The current locale.
+ */
+ return apply_filters( 'imagify_locale', $locale );
+}
+
+/**
+ * Get the label corresponding to the given optimization label.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param int|bool $level Optimization level (between 0 and 2). False if no level.
+ * @param string $format Format to display the label. Use %ICON% for the icon and %s for the label.
+ * @return string The label.
+ */
+function imagify_get_optimization_level_label( $level, $format = '%s' ) {
+ if ( ! is_numeric( $level ) ) {
+ return '';
+ }
+
+ if ( strpos( $format, '%ICON%' ) !== false ) {
+ $icon = '';
+
+ switch ( $level ) {
+ case 2:
+ $icon .= ' ';
+ break;
+ case 1:
+ $icon .= ' ';
+ break;
+ case 0:
+ $icon .= ' ';
+ }
+
+ $icon .= ' ';
+
+ $format = str_replace( '%ICON%', $icon, $format );
+ }
+
+ switch ( $level ) {
+ case 2:
+ return sprintf( $format, __( 'Ultra', 'imagify' ) );
+ case 1:
+ return sprintf( $format, __( 'Aggressive', 'imagify' ) );
+ case 0:
+ return sprintf( $format, __( 'Normal', 'imagify' ) );
+ }
+
+ return '';
+}
+
+/**
+ * `array_merge()` + `array_intersect_key()`.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $values The array we're interested in.
+ * @param array $default The array we use as boundaries.
+ * @return array
+ */
+function imagify_merge_intersect( $values, $default ) {
+ $values = array_merge( $default, (array) $values );
+ return array_intersect_key( $values, $default );
+}
+
+/**
+ * Returns true.
+ * Useful for returning true to filters easily.
+ * Similar to WP's __return_true() function, it allows to remove it from a filter without removing another one added by another plugin.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @return bool True.
+ */
+function imagify_return_true() {
+ return true;
+}
+
+/**
+ * Returns false.
+ * Useful for returning false to filters easily.
+ * Similar to WP's __return_false() function, it allows to remove it from a filter without removing another one added by another plugin.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @return bool False.
+ */
+function imagify_return_false() {
+ return false;
+}
+
+/**
+ * Marks a class as deprecated and informs when it has been used.
+ * Similar to _deprecated_constructor(), but with different strings.
+ * The current behavior is to trigger a user error if `WP_DEBUG` is true.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $class The class containing the deprecated constructor.
+ * @param string $version The version of WordPress that deprecated the function.
+ * @param string $replacement Optional. The function that should have been called. Default null.
+ * @param string $parent_class Optional. The parent class calling the deprecated constructor. Default empty string.
+ */
+function imagify_deprecated_class( $class, $version, $replacement = null, $parent_class = '' ) {
+
+ /**
+ * Fires when a deprecated class is called.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param string $class The class containing the deprecated constructor.
+ * @param string $version The version of WordPress that deprecated the function.
+ * @param string $replacement Optional. The function that should have been called.
+ * @param string $parent_class The parent class calling the deprecated constructor.
+ */
+ do_action( 'imagify_deprecated_class_run', $class, $version, $replacement, $parent_class );
+
+ if ( ! WP_DEBUG ) {
+ return;
+ }
+
+ /**
+ * Filters whether to trigger an error for deprecated classes.
+ *
+ * `WP_DEBUG` must be true in addition to the filter evaluating to true.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param bool $trigger Whether to trigger the error for deprecated classes. Default true.
+ */
+ if ( ! apply_filters( 'imagify_deprecated_class_trigger_error', true ) ) {
+ return;
+ }
+
+ if ( function_exists( '__' ) ) {
+ if ( ! empty( $parent_class ) ) {
+ /**
+ * With parent class.
+ */
+ if ( ! empty( $replacement ) ) {
+ /**
+ * With replacement.
+ */
+ call_user_func(
+ 'trigger_error',
+ sprintf(
+ /* translators: 1: PHP class name, 2: PHP parent class name, 3: version number, 4: replacement class name. */
+ __( 'The called class %1$s extending %2$s is deprecated since version %3$s! Use %4$s instead.', 'imagify' ),
+ '' . $class . '',
+ '' . $parent_class . '',
+ '' . $version . ' ',
+ '' . $replacement . ''
+ )
+ );
+ return;
+ }
+
+ /**
+ * Without replacement.
+ */
+ call_user_func(
+ 'trigger_error',
+ sprintf(
+ /* translators: 1: PHP class name, 2: PHP parent class name, 3: version number. */
+ __( 'The called class %1$s extending %2$s is deprecated since version %3$s!', 'imagify' ),
+ '' . $class . '',
+ '' . $parent_class . '',
+ '' . $version . ' '
+ )
+ );
+ return;
+ }
+
+ /**
+ * Without parent class.
+ */
+ if ( ! empty( $replacement ) ) {
+ /**
+ * With replacement.
+ */
+ call_user_func(
+ 'trigger_error',
+ sprintf(
+ /* translators: 1: PHP class name, 2: version number, 3: replacement class name. */
+ __( 'The called class %1$s is deprecated since version %2$s! Use %3$s instead.', 'imagify' ),
+ '' . $class . '',
+ '' . $version . ' ',
+ '' . $replacement . ''
+ )
+ );
+ return;
+ }
+
+ /**
+ * Without replacement.
+ */
+ call_user_func(
+ 'trigger_error',
+ sprintf(
+ /* translators: 1: PHP class name, 2: version number. */
+ __( 'The called class %1$s is deprecated since version %2$s!', 'imagify' ),
+ '' . $class . '',
+ '' . $version . ' '
+ )
+ );
+ return;
+ }
+
+ if ( ! empty( $parent_class ) ) {
+ /**
+ * With parent class.
+ */
+ if ( ! empty( $replacement ) ) {
+ /**
+ * With replacement.
+ */
+ call_user_func(
+ 'trigger_error',
+ sprintf(
+ 'The called class %1$s extending %2$s is deprecated since version %3$s! Use %4$s instead.',
+ '' . $class . '',
+ '' . $parent_class . '',
+ '' . $version . ' ',
+ '' . $replacement . ''
+ )
+ );
+ return;
+ }
+
+ /**
+ * Without replacement.
+ */
+ call_user_func(
+ 'trigger_error',
+ sprintf(
+ 'The called class %1$s extending %2$s is deprecated since version %3$s!',
+ '' . $class . '',
+ '' . $parent_class . '',
+ '' . $version . ' '
+ )
+ );
+ return;
+ }
+
+ /**
+ * Without parent class.
+ */
+ if ( ! empty( $replacement ) ) {
+ /**
+ * With replacement.
+ */
+ call_user_func(
+ 'trigger_error',
+ sprintf(
+ 'The called class %1$s is deprecated since version %2$s! Use %3$s instead.',
+ '' . $class . '',
+ '' . $version . ' ',
+ '' . $replacement . ''
+ )
+ );
+ return;
+ }
+
+ /**
+ * Without replacement.
+ */
+ call_user_func(
+ 'trigger_error',
+ sprintf(
+ 'The called class %1$s is deprecated since version %2$s!',
+ '' . $class . '',
+ '' . $version . ' '
+ )
+ );
+}
diff --git a/wp-content/plugins/imagify/inc/functions/compat.php b/wp-content/plugins/imagify/inc/functions/compat.php
new file mode 100644
index 00000000..61b7de72
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/compat.php
@@ -0,0 +1,545 @@
+=' ) ) {
+ $args = array( $data, $options, $depth );
+ } elseif ( version_compare( PHP_VERSION, '5.3', '>=' ) ) {
+ $args = array( $data, $options );
+ } else {
+ $args = array( $data );
+ }
+
+ // Prepare the data for JSON serialization.
+ $args[0] = _wp_json_prepare_data( $data );
+
+ $json = @call_user_func_array( 'json_encode', $args );
+
+ // If json_encode() was successful, no need to do more sanity checking.
+ // ... unless we're in an old version of PHP, and json_encode() returned
+ // a string containing 'null'. Then we need to do more sanity checking.
+ if ( false !== $json && ( version_compare( PHP_VERSION, '5.5', '>=' ) || false === strpos( $json, 'null' ) ) ) {
+ return $json;
+ }
+
+ try {
+ $args[0] = _wp_json_sanity_check( $data, $depth );
+ } catch ( Exception $e ) {
+ return false;
+ }
+
+ return call_user_func_array( 'json_encode', $args );
+ }
+endif;
+
+if ( ! function_exists( '_wp_json_prepare_data' ) ) :
+ /**
+ * Prepares response data to be serialized to JSON.
+ *
+ * This supports the JsonSerializable interface for PHP 5.2-5.3 as well.
+ *
+ * @since 1.6.5
+ * @since WP 4.4.0
+ * @access private
+ *
+ * @param mixed $data Native representation.
+ * @return bool|int|float|null|string|array Data ready for `json_encode()`.
+ */
+ function _wp_json_prepare_data( $data ) {
+ if ( ! defined( 'WP_JSON_SERIALIZE_COMPATIBLE' ) || WP_JSON_SERIALIZE_COMPATIBLE === false ) {
+ return $data;
+ }
+
+ switch ( gettype( $data ) ) {
+ case 'boolean':
+ case 'integer':
+ case 'double':
+ case 'string':
+ case 'NULL':
+ // These values can be passed through.
+ return $data;
+
+ case 'array':
+ // Arrays must be mapped in case they also return objects.
+ return array_map( '_wp_json_prepare_data', $data );
+
+ case 'object':
+ // If this is an incomplete object (__PHP_Incomplete_Class), bail.
+ if ( ! is_object( $data ) ) {
+ return null;
+ }
+
+ if ( $data instanceof JsonSerializable ) {
+ $data = $data->jsonSerialize();
+ } else {
+ $data = get_object_vars( $data );
+ }
+
+ // Now, pass the array (or whatever was returned from jsonSerialize through).
+ return _wp_json_prepare_data( $data );
+
+ default:
+ return null;
+ }
+ }
+endif;
+
+if ( ! function_exists( '_wp_json_sanity_check' ) ) :
+ /**
+ * Perform sanity checks on data that shall be encoded to JSON.
+ *
+ * @since 1.6.5
+ * @since WP 4.1.0
+ * @access private
+ * @throws Exception If the depth limit is reached.
+ *
+ * @see wp_json_encode()
+ *
+ * @param mixed $data Variable (usually an array or object) to encode as JSON.
+ * @param int $depth Maximum depth to walk through $data. Must be greater than 0.
+ * @return mixed The sanitized data that shall be encoded to JSON.
+ */
+ function _wp_json_sanity_check( $data, $depth ) {
+ if ( $depth < 0 ) {
+ throw new Exception( 'Reached depth limit' );
+ }
+
+ if ( is_array( $data ) ) {
+ $output = array();
+ foreach ( $data as $id => $el ) {
+ // Don't forget to sanitize the ID!
+ if ( is_string( $id ) ) {
+ $clean_id = _wp_json_convert_string( $id );
+ } else {
+ $clean_id = $id;
+ }
+
+ // Check the element type, so that we're only recursing if we really have to.
+ if ( is_array( $el ) || is_object( $el ) ) {
+ $output[ $clean_id ] = _wp_json_sanity_check( $el, $depth - 1 );
+ } elseif ( is_string( $el ) ) {
+ $output[ $clean_id ] = _wp_json_convert_string( $el );
+ } else {
+ $output[ $clean_id ] = $el;
+ }
+ }
+ } elseif ( is_object( $data ) ) {
+ $output = new stdClass();
+ foreach ( $data as $id => $el ) {
+ if ( is_string( $id ) ) {
+ $clean_id = _wp_json_convert_string( $id );
+ } else {
+ $clean_id = $id;
+ }
+
+ if ( is_array( $el ) || is_object( $el ) ) {
+ $output->$clean_id = _wp_json_sanity_check( $el, $depth - 1 );
+ } elseif ( is_string( $el ) ) {
+ $output->$clean_id = _wp_json_convert_string( $el );
+ } else {
+ $output->$clean_id = $el;
+ }
+ }
+ } elseif ( is_string( $data ) ) {
+ return _wp_json_convert_string( $data );
+ } else {
+ return $data;
+ } // End if().
+
+ return $output;
+ }
+endif;
+
+if ( ! function_exists( '_wp_json_convert_string' ) ) :
+ /**
+ * Convert a string to UTF-8, so that it can be safely encoded to JSON.
+ *
+ * @since 1.6.5
+ * @since WP 4.1.0
+ * @access private
+ *
+ * @see _wp_json_sanity_check()
+ *
+ * @staticvar bool $use_mb
+ *
+ * @param string $string The string which is to be converted.
+ * @return string The checked string.
+ */
+ function _wp_json_convert_string( $string ) {
+ static $use_mb = null;
+ if ( is_null( $use_mb ) ) {
+ $use_mb = function_exists( 'mb_convert_encoding' );
+ }
+
+ if ( $use_mb ) {
+ $encoding = mb_detect_encoding( $string, mb_detect_order(), true );
+ if ( $encoding ) {
+ return mb_convert_encoding( $string, 'UTF-8', $encoding );
+ } else {
+ return mb_convert_encoding( $string, 'UTF-8', 'UTF-8' );
+ }
+ } else {
+ return wp_check_invalid_utf8( $string, true );
+ }
+ }
+endif;
+
+if ( ! function_exists( 'wp_parse_url' ) ) :
+ /**
+ * A wrapper for PHP's parse_url() function that handles consistency in the return
+ * values across PHP versions.
+ *
+ * PHP 5.4.7 expanded parse_url()'s ability to handle non-absolute url's, including
+ * schemeless and relative url's with :// in the path. This function works around
+ * those limitations providing a standard output on PHP 5.2~5.4+.
+ *
+ * Secondly, across various PHP versions, schemeless URLs starting containing a ":"
+ * in the query are being handled inconsistently. This function works around those
+ * differences as well.
+ *
+ * Error suppression is used as prior to PHP 5.3.3, an E_WARNING would be generated
+ * when URL parsing failed.
+ *
+ * @since 1.6.9
+ * @since WP 4.4.0
+ * @since WP 4.7.0 The $component parameter was added for parity with PHP's parse_url().
+ *
+ * @param (string) $url The URL to parse.
+ * @param (int) $component The specific component to retrieve. Use one of the PHP
+ * predefined constants to specify which one.
+ * Defaults to -1 (= return all parts as an array).
+ * @see http://php.net/manual/en/function.parse-url.php
+ *
+ * @return (mixed) False on parse failure; Array of URL components on success;
+ * When a specific component has been requested: null if the component
+ * doesn't exist in the given URL; a sting or - in the case of
+ * PHP_URL_PORT - integer when it does. See parse_url()'s return values.
+ */
+ function wp_parse_url( $url, $component = -1 ) {
+ $to_unset = array();
+ $url = strval( $url );
+
+ if ( '//' === substr( $url, 0, 2 ) ) {
+ $to_unset[] = 'scheme';
+ $url = 'placeholder:' . $url;
+ } elseif ( '/' === substr( $url, 0, 1 ) ) {
+ $to_unset[] = 'scheme';
+ $to_unset[] = 'host';
+ $url = 'placeholder://placeholder' . $url;
+ }
+
+ $parts = @parse_url( $url );
+
+ if ( false === $parts ) {
+ // Parsing failure.
+ return $parts;
+ }
+
+ // Remove the placeholder values.
+ if ( $to_unset ) {
+ foreach ( $to_unset as $key ) {
+ unset( $parts[ $key ] );
+ }
+ }
+
+ return _get_component_from_parsed_url_array( $parts, $component );
+ }
+endif;
+
+if ( ! function_exists( '_get_component_from_parsed_url_array' ) ) :
+ /**
+ * Retrieve a specific component from a parsed URL array.
+ *
+ * @since 1.6.9
+ * @since WP 4.7.0
+ *
+ * @param (array|false) $url_parts The parsed URL. Can be false if the URL failed to parse.
+ * @param (int) $component The specific component to retrieve. Use one of the PHP
+ * predefined constants to specify which one.
+ * Defaults to -1 (= return all parts as an array).
+ * @see http://php.net/manual/en/function.parse-url.php
+ *
+ * @return (mixed) False on parse failure; Array of URL components on success;
+ * When a specific component has been requested: null if the component
+ * doesn't exist in the given URL; a sting or - in the case of
+ * PHP_URL_PORT - integer when it does. See parse_url()'s return values.
+ */
+ function _get_component_from_parsed_url_array( $url_parts, $component = -1 ) {
+ if ( -1 === $component ) {
+ return $url_parts;
+ }
+
+ $key = _wp_translate_php_url_constant_to_key( $component );
+
+ if ( false !== $key && is_array( $url_parts ) && isset( $url_parts[ $key ] ) ) {
+ return $url_parts[ $key ];
+ } else {
+ return null;
+ }
+ }
+endif;
+
+if ( ! function_exists( '_wp_translate_php_url_constant_to_key' ) ) :
+ /**
+ * Translate a PHP_URL_* constant to the named array keys PHP uses.
+ *
+ * @since 1.6.9
+ * @since WP 4.7.0
+ * @see http://php.net/manual/en/url.constants.php
+ *
+ * @param (int) $constant PHP_URL_* constant.
+ *
+ * @return (string|bool) The named key or false.
+ */
+ function _wp_translate_php_url_constant_to_key( $constant ) {
+ $translation = array(
+ PHP_URL_SCHEME => 'scheme',
+ PHP_URL_HOST => 'host',
+ PHP_URL_PORT => 'port',
+ PHP_URL_USER => 'user',
+ PHP_URL_PASS => 'pass',
+ PHP_URL_PATH => 'path',
+ PHP_URL_QUERY => 'query',
+ PHP_URL_FRAGMENT => 'fragment',
+ );
+
+ if ( isset( $translation[ $constant ] ) ) {
+ return $translation[ $constant ];
+ } else {
+ return false;
+ }
+ }
+endif;
+
+if ( ! function_exists( 'wp_get_additional_image_sizes' ) ) :
+ /**
+ * Retrieve additional image sizes.
+ *
+ * @since 1.6.10
+ * @since WP 4.7.0
+ *
+ * @global array $_wp_additional_image_sizes
+ *
+ * @return array Additional images size data.
+ */
+ function wp_get_additional_image_sizes() {
+ global $_wp_additional_image_sizes;
+ if ( ! $_wp_additional_image_sizes ) {
+ $_wp_additional_image_sizes = array(); // WPCS: override ok.
+ }
+ return $_wp_additional_image_sizes;
+ }
+endif;
+
+if ( ! function_exists( 'wp_scripts' ) ) :
+ /**
+ * Initialize $wp_scripts if it has not been set.
+ *
+ * @global WP_Scripts $wp_scripts
+ *
+ * @since 1.6.11
+ * @since WP 4.2.0
+ *
+ * @return WP_Scripts WP_Scripts instance.
+ */
+ function wp_scripts() {
+ global $wp_scripts;
+ if ( ! ( $wp_scripts instanceof WP_Scripts ) ) {
+ $wp_scripts = new WP_Scripts(); // WPCS: override ok.
+ }
+ return $wp_scripts;
+ }
+endif;
+
+if ( ! function_exists( 'wp_doing_ajax' ) ) :
+ /**
+ * Determines whether the current request is a WordPress Ajax request.
+ *
+ * @since 1.7
+ * @since WP 4.7.0
+ *
+ * @return bool True if it's a WordPress Ajax request, false otherwise.
+ */
+ function wp_doing_ajax() {
+ /**
+ * Filters whether the current request is a WordPress Ajax request.
+ *
+ * @since 1.7
+ * @since WP 4.7.0
+ *
+ * @param bool $wp_doing_ajax Whether the current request is a WordPress Ajax request.
+ */
+ return apply_filters( 'wp_doing_ajax', defined( 'DOING_AJAX' ) && DOING_AJAX );
+ }
+endif;
+
+if ( ! function_exists( '_deprecated_hook' ) ) :
+ /**
+ * Marks a deprecated action or filter hook as deprecated and throws a notice.
+ *
+ * Use the {@see 'deprecated_hook_run'} action to get the backtrace describing where
+ * the deprecated hook was called.
+ *
+ * Default behavior is to trigger a user error if `WP_DEBUG` is true.
+ *
+ * This function is called by the do_action_deprecated() and apply_filters_deprecated()
+ * functions, and so generally does not need to be called directly.
+ *
+ * @since 1.7
+ * @since WP 4.6.0
+ * @access private
+ *
+ * @param string $hook The hook that was used.
+ * @param string $version The version of WordPress that deprecated the hook.
+ * @param string $replacement Optional. The hook that should have been used.
+ * @param string $message Optional. A message regarding the change.
+ */
+ function _deprecated_hook( $hook, $version, $replacement = null, $message = null ) {
+ /**
+ * Fires when a deprecated hook is called.
+ *
+ * @since 1.7
+ * @since WP 4.6.0
+ *
+ * @param string $hook The hook that was called.
+ * @param string $replacement The hook that should be used as a replacement.
+ * @param string $version The version of WordPress that deprecated the argument used.
+ * @param string $message A message regarding the change.
+ */
+ do_action( 'deprecated_hook_run', $hook, $replacement, $version, $message );
+
+ /**
+ * Filters whether to trigger deprecated hook errors.
+ *
+ * @since 1.7
+ * @since WP 4.6.0
+ *
+ * @param bool $trigger Whether to trigger deprecated hook errors. Requires
+ * `WP_DEBUG` to be defined true.
+ */
+ if ( WP_DEBUG && apply_filters( 'deprecated_hook_trigger_error', true ) ) {
+ $message = empty( $message ) ? '' : ' ' . $message;
+ if ( ! is_null( $replacement ) ) {
+ /* translators: 1: WordPress hook name, 2: version number, 3: alternative hook name */
+ trigger_error( sprintf( __( '%1$s is deprecated since version %2$s! Use %3$s instead.', 'imagify' ), $hook, $version, $replacement ) . $message );
+ } else {
+ /* translators: 1: WordPress hook name, 2: version number */
+ trigger_error( sprintf( __( '%1$s is deprecated since version %2$s with no alternative available.', 'imagify' ), $hook, $version ) . $message );
+ }
+ }
+ }
+endif;
+
+if ( ! function_exists( 'do_action_deprecated' ) ) :
+ /**
+ * Fires functions attached to a deprecated action hook.
+ *
+ * When an action hook is deprecated, the do_action() call is replaced with
+ * do_action_deprecated(), which triggers a deprecation notice and then fires
+ * the original hook.
+ *
+ * @since 1.9
+ * @since WP 4.6.0
+ *
+ * @see _deprecated_hook()
+ *
+ * @param string $tag The name of the action hook.
+ * @param array $args Array of additional function arguments to be passed to do_action().
+ * @param string $version The version of WordPress that deprecated the hook.
+ * @param string $replacement Optional. The hook that should have been used.
+ * @param string $message Optional. A message regarding the change.
+ */
+ function do_action_deprecated( $tag, $args, $version, $replacement = false, $message = null ) {
+ if ( ! has_action( $tag ) ) {
+ return;
+ }
+
+ _deprecated_hook( $tag, $version, $replacement, $message );
+
+ do_action_ref_array( $tag, $args );
+ }
+endif;
+
+if ( ! function_exists( 'apply_filters_deprecated' ) ) :
+ /**
+ * Fires functions attached to a deprecated filter hook.
+ *
+ * When a filter hook is deprecated, the apply_filters() call is replaced with
+ * apply_filters_deprecated(), which triggers a deprecation notice and then fires
+ * the original filter hook.
+ *
+ * Note: the value and extra arguments passed to the original apply_filters() call
+ * must be passed here to `$args` as an array. For example:
+ *
+ * // Old filter.
+ * return apply_filters( 'wpdocs_filter', $value, $extra_arg );
+ *
+ * // Deprecated.
+ * return apply_filters_deprecated( 'wpdocs_filter', array( $value, $extra_arg ), '4.9', 'wpdocs_new_filter' );
+ *
+ * @since 1.7
+ * @since WP 4.6.0
+ *
+ * @see _deprecated_hook()
+ *
+ * @param string $tag The name of the filter hook.
+ * @param array $args Array of additional function arguments to be passed to apply_filters().
+ * @param string $version The version of WordPress that deprecated the hook.
+ * @param string $replacement Optional. The hook that should have been used. Default false.
+ * @param string $message Optional. A message regarding the change. Default null.
+ */
+ function apply_filters_deprecated( $tag, $args, $version, $replacement = false, $message = null ) {
+ if ( ! has_filter( $tag ) ) {
+ return $args[0];
+ }
+
+ _deprecated_hook( $tag, $version, $replacement, $message );
+
+ return apply_filters_ref_array( $tag, $args );
+ }
+endif;
diff --git a/wp-content/plugins/imagify/inc/functions/formatting.php b/wp-content/plugins/imagify/inc/functions/formatting.php
new file mode 100644
index 00000000..9fb36c63
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/formatting.php
@@ -0,0 +1,57 @@
+ 0 ) {
+ if ( $decimal <= 0.5 ) {
+ return floatval( $number[0] ) + 0.5;
+ }
+ if ( $decimal <= 0.99 ) {
+ return floatval( $number[0] ) + 1;
+ }
+ return 1;
+ }
+
+ return floatval( $number );
+}
+
+/**
+ * Convert number of bytes largest unit bytes will fit into.
+ * This is a clone of size_format(), but with a non-breaking space.
+ *
+ * @since 1.7
+ * @since 1.8.1 Automatic $decimals.
+ * @author Grégory Viguier
+ *
+ * @param int|string $bytes Number of bytes. Note max integer size for integers.
+ * @param int $decimals Optional. Precision of number of decimal places.
+ * If negative or not an integer, $decimals value is "automatic": 0 if $bytes <= 1GB, or 1 if > 1GB.
+ * @return string|false False on failure. Number string on success.
+ */
+function imagify_size_format( $bytes, $decimals = -1 ) {
+
+ if ( $decimals < 0 || ! is_int( $decimals ) ) {
+ $decimals = $bytes > pow( 1024, 3 ) ? 1 : 0;
+ }
+
+ $bytes = @size_format( $bytes, $decimals );
+ return str_replace( ' ', 'Â ', $bytes );
+}
diff --git a/wp-content/plugins/imagify/inc/functions/i18n.php b/wp-content/plugins/imagify/inc/functions/i18n.php
new file mode 100644
index 00000000..e5825c07
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/i18n.php
@@ -0,0 +1,327 @@
+ admin_url( 'admin-ajax.php' ),
+ ];
+
+ case 'notices':
+ return [
+ 'labels' => [
+ /* translators: Don't use escaped HTML entities here (like ). */
+ 'signupTitle' => __( 'Let\'s get you started!', 'imagify' ),
+ 'signupText' => __( 'Enter your email to get an API key:', 'imagify' ),
+ 'signupConfirmButtonText' => __( 'Sign Up', 'imagify' ),
+ 'signupErrorEmptyEmail' => __( 'You need to specify an email!', 'imagify' ),
+ /* translators: Don't use escaped HTML entities here (like ). */
+ 'signupSuccessTitle' => __( 'Congratulations!', 'imagify' ),
+ 'signupSuccessText' => __( 'Your account has been successfully created. Please check your mailbox, you are going to receive an email with API key.', 'imagify' ),
+ /* translators: Don't use escaped HTML entities here (like ). */
+ 'saveApiKeyTitle' => __( 'Connect to Imagify!', 'imagify' ),
+ 'saveApiKeyText' => __( 'Paste your API key below:', 'imagify' ),
+ 'saveApiKeyConfirmButtonText' => __( 'Connect me', 'imagify' ),
+ 'ApiKeyErrorEmpty' => __( 'You need to specify your api key!', 'imagify' ),
+ 'ApiKeyCheckSuccessTitle' => __( 'Congratulations!', 'imagify' ),
+ 'ApiKeyCheckSuccessText' => __( 'Your API key is valid. You can now configure the Imagify settings to optimize your images.', 'imagify' ),
+ ],
+ ];
+
+ case 'sweetalert':
+ return [
+ 'labels' => [
+ 'cancelButtonText' => __( 'Cancel' ),
+ ],
+ ];
+
+ case 'options':
+ $translations = [
+ 'getFilesTree' => imagify_can_optimize_custom_folders() ? get_imagify_admin_url( 'get-files-tree' ) : false,
+ 'labels' => [
+ 'ValidApiKeyText' => __( 'Your API key is valid.', 'imagify' ),
+ 'waitApiKeyCheckText' => __( 'Check in progress...', 'imagify' ),
+ 'ApiKeyCheckSuccessTitle' => __( 'Congratulations!', 'imagify' ),
+ 'ApiKeyCheckSuccessText' => __( 'Your API key is valid. You can now configure the Imagify settings to optimize your images.', 'imagify' ),
+ 'noBackupTitle' => __( 'Don\'t Need a Parachute?', 'imagify' ),
+ 'noBackupText' => __( 'If you keep this option deactivated, you won\'t be able to re-optimize your images to another compression level and restore your original images in case of need.', 'imagify' ),
+ 'removeFolder' => _x( 'Remove', 'custom folder', 'imagify' ),
+ 'filesTreeTitle' => __( 'Select Folders', 'imagify' ),
+ 'filesTreeSubTitle' => __( 'Select one or several folders to optimize.', 'imagify' ),
+ 'cleaningInfo' => __( 'Some folders that do not contain any images are hidden.', 'imagify' ),
+ 'confirmFilesTreeBtn' => __( 'Select Folders', 'imagify' ),
+ 'customFilesLegend' => __( 'Choose the folders to optimize', 'imagify' ),
+ 'error' => __( 'Error', 'imagify' ),
+ 'themesAdded' => __( 'Added! All Good!', 'imagify' ),
+ ],
+ ];
+
+ if ( \Imagify\Stats\OptimizedMediaWithoutWebp::get_instance()->get_cached_stat() ) {
+ $contexts = imagify_get_context_names();
+ $translations['bulk'] = [
+ 'curlMissing' => ! Imagify_Requirements::supports_curl(),
+ 'editorMissing' => ! Imagify_Requirements::supports_image_editor(),
+ 'extHttpBlocked' => Imagify_Requirements::is_imagify_blocked(),
+ 'apiDown' => ! Imagify_Requirements::is_api_up(),
+ 'keyIsValid' => Imagify_Requirements::is_api_key_valid(),
+ 'isOverQuota' => Imagify_Requirements::is_over_quota(),
+ 'imagifybeatIDs' => [
+ 'queue' => $imagifybeat_actions->get_imagifybeat_id( 'bulk_optimization_status' ),
+ 'requirements' => $imagifybeat_actions->get_imagifybeat_id( 'requirements' ),
+ ],
+ 'ajaxActions' => [
+ 'getMediaIds' => 'imagify_get_media_ids',
+ 'bulkProcess' => 'imagify_bulk_optimize',
+ ],
+ 'ajaxNonce' => wp_create_nonce( 'imagify-bulk-optimize' ),
+ 'contexts' => $contexts,
+ 'labels' => [
+ 'curlMissing' => __( 'cURL is not available on the server.', 'imagify' ),
+ 'editorMissing' => sprintf(
+ /* translators: %s is a "More info?" link. */
+ __( 'No php extensions are available to edit images on the server. ImageMagick or GD is required. %s', 'imagify' ),
+ '' . __( 'More info?', 'imagify' ) . ' '
+ ),
+ 'extHttpBlocked' => __( 'External HTTP requests are blocked.', 'imagify' ),
+ 'apiDown' => __( 'Sorry, our servers are temporarily unavailable. Please, try again in a couple of minutes.', 'imagify' ),
+ 'invalidAPIKeyTitle' => __( 'Your API key is not valid!', 'imagify' ),
+ 'overQuotaTitle' => __( 'You have used all your credits!', 'imagify' ),
+ 'processing' => __( 'Imagify is still processing. Are you sure you want to leave this page?', 'imagify' ),
+ 'nothingToDoTitle' => __( 'Hold on!', 'imagify' ),
+ 'nothingToDoText' => __( 'All your optimized images already have a webp version. Congratulations!', 'imagify' ),
+ 'nothingToDoNoBackupText' => __( 'Because the selected images did not have a backup copy, Imagify was unable to create webp versions.', 'imagify' ),
+ 'error' => __( 'Error', 'imagify' ),
+ 'ajaxErrorText' => __( 'The operation failed.', 'imagify' ),
+ 'getUnoptimizedImagesErrorTitle' => __( 'Oops, There is something wrong!', 'imagify' ),
+ 'getUnoptimizedImagesErrorText' => __( 'An unknown error occurred when we tried to get all your unoptimized media files. Try again and if the issue still persists, please contact us!', 'imagify' ),
+ ],
+ ];
+
+ /**
+ * Filter the number of parallel queries generating webp images by bulk method.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ *
+ * @param int $bufferSize Number of parallel queries.
+ */
+ $translations['bulk']['bufferSize'] = apply_filters( 'imagify_bulk_generate_webp_buffer_size', 4 );
+ $translations['bulk']['bufferSize'] = max( 1, (int) $translations['bulk']['bufferSize'] );
+ }
+
+ return $translations;
+
+ case 'pricing-modal':
+ $translations = [
+ 'labels' => [
+ 'errorCouponAPI' => __( 'Error with checking this coupon.', 'imagify' ),
+ /* translators: 1 is a percentage, 2 is a coupon code. */
+ 'successCouponAPI' => sprintf( _x( '%1$s off with %2$s', 'coupon validated', 'imagify' ), ' ', ' ' ),
+ 'errorPriceAPI' => __( 'Something went wrong with getting our updated offers. Please retry later.', 'imagify' ),
+ ],
+ ];
+
+ if ( Imagify_Requirements::is_api_key_valid() ) {
+ $translations['userDataCache'] = [
+ 'deleteAction' => 'imagify_delete_user_data_cache',
+ 'deleteNonce' => wp_create_nonce( 'imagify_delete_user_data_cache' ),
+ ];
+ }
+
+ return $translations;
+
+ case 'twentytwenty':
+ $image = [
+ 'src' => '',
+ 'width' => 0,
+ 'height' => 0,
+ ];
+
+ if ( imagify_is_screen( 'attachment' ) && wp_attachment_is_image( $post_id ) ) {
+ $process = imagify_get_optimization_process( $post_id, 'wp' );
+
+ if ( $process->is_valid() ) {
+ $media = $process->get_media();
+
+ if ( $media->is_image() ) {
+ $dimensions = $media->get_dimensions();
+ $image = [
+ 'src' => $media->get_fullsize_url(),
+ 'width' => $dimensions['width'],
+ 'height' => $dimensions['height'],
+ ];
+ }
+ }
+ }
+
+ return [
+ 'imageSrc' => $image['src'],
+ 'imageWidth' => $image['width'],
+ 'imageHeight' => $image['height'],
+ 'widthLimit' => 360, // See _imagify_add_actions_to_media_list_row().
+ 'labels' => [
+ 'filesize' => __( 'File Size:', 'imagify' ),
+ 'saving' => __( 'Original Saving:', 'imagify' ),
+ 'close' => __( 'Close', 'imagify' ),
+ 'originalL' => __( 'Original File', 'imagify' ),
+ 'optimizedL' => __( 'Optimized File', 'imagify' ),
+ 'compare' => __( 'Compare Original VS Optimized', 'imagify' ),
+ 'optimize' => __( 'Optimize', 'imagify' ),
+ ],
+ ];
+
+ case 'beat':
+ return \Imagify\Imagifybeat\Core::get_instance()->get_settings();
+
+ case 'media-modal':
+ return [
+ 'imagifybeatID' => $imagifybeat_actions->get_imagifybeat_id( 'library_optimization_status' ),
+ ];
+
+ case 'library':
+ return [
+ 'backupOption' => get_imagify_option( 'backup' ),
+ 'labels' => [
+ 'bulkActionsOptimize' => __( 'Optimize', 'imagify' ),
+ 'bulkActionsOptimizeMissingSizes' => __( 'Optimize Missing Sizes', 'imagify' ),
+ 'bulkActionsRestore' => __( 'Restore Original', 'imagify' ),
+ ],
+ ];
+
+ case 'bulk':
+ $translations = [
+ 'curlMissing' => ! Imagify_Requirements::supports_curl(),
+ 'editorMissing' => ! Imagify_Requirements::supports_image_editor(),
+ 'extHttpBlocked' => Imagify_Requirements::is_imagify_blocked(),
+ 'apiDown' => Imagify_Requirements::is_imagify_blocked() || ! Imagify_Requirements::is_api_up(),
+ 'keyIsValid' => ! Imagify_Requirements::is_imagify_blocked() && Imagify_Requirements::is_api_up() && Imagify_Requirements::is_api_key_valid(),
+ 'isOverQuota' => ! Imagify_Requirements::is_imagify_blocked() && Imagify_Requirements::is_api_up() && Imagify_Requirements::is_api_key_valid() && Imagify_Requirements::is_over_quota(),
+ 'imagifybeatIDs' => [
+ 'stats' => $imagifybeat_actions->get_imagifybeat_id( 'bulk_optimization_stats' ),
+ 'queue' => $imagifybeat_actions->get_imagifybeat_id( 'bulk_optimization_status' ),
+ 'requirements' => $imagifybeat_actions->get_imagifybeat_id( 'requirements' ),
+ ],
+ 'waitImageUrl' => IMAGIFY_ASSETS_IMG_URL . 'popin-loader.svg',
+ 'ajaxActions' => [
+ 'getMediaIds' => 'imagify_get_media_ids',
+ 'bulkProcess' => 'imagify_bulk_optimize',
+ 'getFolderData' => 'imagify_get_folder_type_data',
+ 'bulkInfoSeen' => 'imagify_bulk_info_seen',
+ ],
+ 'ajaxNonce' => wp_create_nonce( 'imagify-bulk-optimize' ),
+ 'bufferSizes' => [
+ 'wp' => 4,
+ 'custom-folders' => 4,
+ ],
+ 'labels' => [
+ 'overviewChartLabels' => [
+ 'unoptimized' => __( 'Unoptimized', 'imagify' ),
+ 'optimized' => __( 'Optimized', 'imagify' ),
+ 'error' => __( 'Error', 'imagify' ),
+ ],
+ 'curlMissing' => __( 'cURL is not available on the server.', 'imagify' ),
+ 'editorMissing' => sprintf(
+ /* translators: %s is a "More info?" link. */
+ __( 'No php extensions are available to edit images on the server. ImageMagick or GD is required. %s', 'imagify' ),
+ '' . __( 'More info?', 'imagify' ) . ' '
+ ),
+ 'extHttpBlocked' => __( 'External HTTP requests are blocked.', 'imagify' ),
+ 'apiDown' => __( 'Sorry, our servers are temporarily unavailable. Please, try again in a couple of minutes.', 'imagify' ),
+ 'invalidAPIKeyTitle' => __( 'Your API key is not valid!', 'imagify' ),
+ 'overQuotaTitle' => __( 'You have used all your credits!', 'imagify' ),
+ 'processing' => __( 'Imagify is still processing. Are you sure you want to leave this page?', 'imagify' ),
+ 'waitTitle' => __( 'Please wait...', 'imagify' ),
+ 'waitText' => __( 'We are trying to get your unoptimized media files, it may take time depending on the number of files.', 'imagify' ),
+ 'nothingToDoTitle' => __( 'Hold on!', 'imagify' ),
+ 'nothingToDoText' => [
+ 'optimize' => __( 'All your media files have been optimized by Imagify. Congratulations!', 'imagify' ),
+ 'generate_webp' => __( 'All your optimized images already have a webp version. Congratulations!', 'imagify' ),
+ ],
+ 'optimizing' => __( 'Optimizing', 'imagify' ),
+ 'error' => __( 'Error', 'imagify' ),
+ 'ajaxErrorText' => __( 'The operation failed.', 'imagify' ),
+ 'complete' => _x( 'Complete', 'adjective', 'imagify' ),
+ 'alreadyOptimized' => _x( 'Already Optimized', 'file', 'imagify' ),
+ /* translators: %s is a number. Don't use %d. */
+ 'nbrFiles' => __( '%s file(s)', 'imagify' ),
+ 'notice' => _x( 'Notice', 'noun', 'imagify' ),
+ /* translators: %s is a number. Don't use %d. */
+ 'nbrErrors' => __( '%s error(s)', 'imagify' ),
+ /* translators: 1 and 2 are file sizes. Don't use HTML entities here (like ). */
+ 'textToShare' => __( 'Discover @imagify, the new compression tool to optimize your images for free. I saved %1$s out of %2$s!', 'imagify' ),
+ 'twitterShareURL' => imagify_get_external_url( 'share-twitter' ),
+ 'getUnoptimizedImagesErrorTitle' => __( 'Oops, There is something wrong!', 'imagify' ),
+ 'getUnoptimizedImagesErrorText' => __( 'An unknown error occurred when we tried to get all your unoptimized media files. Try again and if the issue still persists, please contact us!', 'imagify' ),
+ 'waitingOtimizationsText' => __( 'Waiting other optimizations to finish.', 'imagify' ),
+ /* translators: %s is a formatted number, dont use %d. */
+ 'imagesOptimizedText' => __( '%s Media File(s) Optimized', 'imagify' ),
+ /* translators: %s is a formatted number, dont use %d. */
+ 'imagesErrorText' => __( '%s Error(s)', 'imagify' ),
+ 'bulkInfoTitle' => __( 'Information', 'imagify' ),
+ 'confirmBulk' => __( 'Start the optimization', 'imagify' ),
+ ],
+ ];
+
+ if ( get_transient( 'imagify_large_library' ) ) {
+ // On huge media libraries, don't use Imagifybeat, and fetch stats only when the process ends.
+ $translations['ajaxActions']['getStats'] = 'imagify_bulk_get_stats';
+ }
+
+ if ( isset( $translations['bufferSizes']['wp'] ) ) {
+ /**
+ * Filter the number of parallel queries during the Bulk Optimization (library).
+ *
+ * @since 1.5.4
+ * @since 1.7 Deprecated
+ * @deprecated
+ *
+ * @param int $buffer_size Number of parallel queries.
+ */
+ $translations['bufferSizes']['wp'] = apply_filters_deprecated( 'imagify_bulk_buffer_size', [ $translations['bufferSizes']['wp'] ], '1.7', 'imagify_bulk_buffer_sizes' );
+ }
+
+ /**
+ * Filter the number of parallel queries during the Bulk Optimization.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ *
+ * @param array $buffer_sizes An array of number of parallel queries, keyed by context.
+ */
+ $translations['bufferSizes'] = apply_filters( 'imagify_bulk_buffer_sizes', $translations['bufferSizes'] );
+
+ return $translations;
+
+ case 'files-list':
+ return [
+ 'backupOption' => get_imagify_option( 'backup' ),
+ 'context' => 'custom-folders',
+ 'imagifybeatID' => $imagifybeat_actions->get_imagifybeat_id( 'custom_folders_optimization_status' ),
+ 'labels' => [
+ 'bulkActionsOptimize' => __( 'Optimize', 'imagify' ),
+ 'bulkActionsRestore' => __( 'Restore Original', 'imagify' ),
+ ],
+ ];
+
+ default:
+ return [];
+ } // End switch().
+}
diff --git a/wp-content/plugins/imagify/inc/functions/media.php b/wp-content/plugins/imagify/inc/functions/media.php
new file mode 100644
index 00000000..e532ac8b
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/media.php
@@ -0,0 +1,22 @@
+get( $key );
+}
+
+/**
+ * Update an Imagify option.
+ *
+ * @since 1.6
+ * @author Remy Perona
+ *
+ * @param string $key The option name.
+ * @param mixed $value The value of the option.
+ */
+function update_imagify_option( $key, $value ) {
+ Imagify_Options::get_instance()->set( $key, $value );
+}
+
+/**
+ * Autoload network options and put them in cache.
+ *
+ * @since 1.7
+ * @author Grégory Viguier
+ * @source SecuPress
+ *
+ * @param array $option_names A list of option names to cache.
+ * @param string|array $prefixes A prefix for the option names. Handy for transients for example (`_site_transient_` and `_site_transient_timeout_`).
+ */
+function imagify_load_network_options( $option_names, $prefixes = '' ) {
+ global $wpdb;
+
+ $prefixes = (array) $prefixes;
+
+ if ( ! $option_names || count( $option_names ) * count( $prefixes ) === 1 ) {
+ return;
+ }
+
+ // Get values.
+ $not_exist = array();
+ $option_names = array_flip( array_flip( $option_names ) );
+ $options = '';
+
+ foreach ( $prefixes as $prefix ) {
+ $options .= "'$prefix" . implode( "','$prefix", esc_sql( $option_names ) ) . "',";
+ }
+
+ $options = rtrim( $options, ',' );
+
+ if ( is_multisite() ) {
+ $network_id = function_exists( 'get_current_network_id' ) ? get_current_network_id() : (int) $wpdb->siteid;
+ $cache_prefix = "$network_id:";
+ $notoptions_key = "$network_id:notoptions";
+ $cache_group = 'site-options';
+ $results = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key as name, meta_value as value FROM $wpdb->sitemeta WHERE meta_key IN ( $options ) AND site_id = %d", $network_id ), OBJECT_K ); // WPCS: unprepared SQL ok.
+ } else {
+ $cache_prefix = '';
+ $notoptions_key = 'notoptions';
+ $cache_group = 'options';
+ $results = $wpdb->get_results( "SELECT option_name as name, option_value as value FROM $wpdb->options WHERE option_name IN ( $options )", OBJECT_K ); // WPCS: unprepared SQL ok.
+ }
+
+ foreach ( $prefixes as $prefix ) {
+ foreach ( $option_names as $option_name ) {
+ $option_name = $prefix . $option_name;
+
+ if ( isset( $results[ $option_name ] ) ) {
+ // Cache the value.
+ $value = $results[ $option_name ]->value;
+ $value = maybe_unserialize( $value );
+ wp_cache_set( "$cache_prefix$option_name", $value, $cache_group );
+ } else {
+ // No value.
+ $not_exist[ $option_name ] = true;
+ }
+ }
+ }
+
+ if ( ! $not_exist ) {
+ return;
+ }
+
+ // Cache the options that don't exist in the DB.
+ $notoptions = wp_cache_get( $notoptions_key, $cache_group );
+ $notoptions = is_array( $notoptions ) ? $notoptions : array();
+ $notoptions = array_merge( $notoptions, $not_exist );
+
+ wp_cache_set( $notoptions_key, $notoptions, $cache_group );
+}
diff --git a/wp-content/plugins/imagify/inc/functions/partners.php b/wp-content/plugins/imagify/inc/functions/partners.php
new file mode 100644
index 00000000..449c3c21
--- /dev/null
+++ b/wp-content/plugins/imagify/inc/functions/partners.php
@@ -0,0 +1,38 @@
+ 0.01,
+ 'blocking' => false,
+ 'body' => $body,
+ 'cookies' => isset( $_COOKIE ) && is_array( $_COOKIE ) ? $_COOKIE : [],
+ 'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
+ ];
+
+ /**
+ * Filter the arguments used to launch an async job.
+ *
+ * @since 1.6.6
+ * @author Grégory Viguier
+ *
+ * @param array $args An array of arguments passed to wp_remote_post().
+ */
+ $args = apply_filters( 'imagify_do_async_job_args', $args );
+
+ /**
+ * It can be a XML-RPC request. The problem is that XML-RPC doesn't use cookies.
+ */
+ if ( defined( 'XMLRPC_REQUEST' ) && get_current_user_id() ) {
+ /**
+ * In old WP versions, the field "option_name" in the wp_options table was limited to 64 characters.
+ * From 64, remove 19 characters for "_transient_timeout_" = 45.
+ * Then remove 12 characters for "imagify_rpc_" (transient name) = 33.
+ * Luckily, a md5 is 32 characters long.
+ */
+ $rpc_id = md5( maybe_serialize( $body ) );
+
+ // Send the request to our RPC bridge instead.
+ $args['body']['imagify_rpc_action'] = $args['body']['action'];
+ $args['body']['action'] = 'imagify_rpc';
+ $args['body']['imagify_rpc_id'] = $rpc_id;
+ $args['body']['imagify_rpc_nonce'] = wp_create_nonce( 'imagify_rpc_' . $rpc_id );
+
+ // Since we can't send cookies to keep the user logged in, store the user ID in a transient.
+ set_transient( 'imagify_rpc_' . $rpc_id, get_current_user_id(), 30 );
+ }
+
+ $url = admin_url( 'admin-ajax.php' );
+
+ /**
+ * Filter the URL to use for async jobs.
+ *
+ * @since 1.9.5
+ * @author Grégory Viguier
+ *
+ * @param string $url An URL.
+ * @param array $args An array of arguments passed to wp_remote_post().
+ */
+ $url = apply_filters( 'imagify_async_job_url', $url, $args );
+
+ wp_remote_post( $url, $args );
+}
diff --git a/wp-content/plugins/imagify/readme.txt b/wp-content/plugins/imagify/readme.txt
new file mode 100644
index 00000000..a0f900e0
--- /dev/null
+++ b/wp-content/plugins/imagify/readme.txt
@@ -0,0 +1,687 @@
+=== Imagify â Optimize your Images & Convert WebP ===
+Contributors: wp_media
+Tags: convert webp, webp, optimize images, optimize, images
+Requires at least: 4.0.0
+Tested up to: 5.6.1
+Stable tag: 1.9.14
+
+Optimize images in one click: reduce image file sizes, convert WebP, keep your images beautiful⦠and boost your loading time and your SEO!
+
+== Description ==
+
+Optimize images in one click: get lighter images without losing quality, convert WebP and speed up your website!
+
+Imagify is the most advanced tool to optimize images. You can now use this power directly in WordPress.
+
+After enabling it, all your images including thumbnails will be automatically optimized when uploaded into WordPress. You can also use Imagify to convert WebP images, which will additionally reduce the size of your website making it faster.
+
+WooCommerce and NextGen Gallery compatible.
+
+= Why is it Important to Optimize Images? =
+
+A fast site is important primarily for visitors who may leave a website that is too slow, but also for SEO performance since search engines consider website speed as a ranking factor.
+
+Website size is one of the most important factors that affect the website performance: images can account for 50% of your loading time.
+
+By optimizing images you will quickly gain precious seconds and make your website faster.
+
+Learn more about image compression, check that: [https://imagify.io/images-compression](https://imagify.io/images-compression).
+
+= Why use Imagify to optimize images? =
+
+Imagify can optimize all images: jpgs, pngs, pdfs and gifs (whether animated or not).
+
+You already have lots of unoptimized images? Not a problem, you will love the Bulk Optimizer to optimize all your existing images in one click.
+
+Imagify can directly resize your images, **you won't have to lose time anymore on resizing your images before uploading them**.
+
+There are three optimization levels available - Normal, Aggressive and Ultra.
+
+- Normal, a lossless compression algorithm. The image quality won't be altered at all.
+- Aggressive, a lossy compression algorithm. Stronger compression with a tiny loss of quality most of the time this is not even noticeable at all.
+- Ultra, our strongest compression method using a lossy algorithm.
+
+With the backup option, you can change your mind whenever you want by restoring your images to their original version or optimize them to another compression level.
+
+= HOW ABOUT WEBP IMAGES? =
+Now, for each image you optimize with the Imagify plugin, you will also get its **WebP version** (if you tick the option in the settings); in your Media library, this will result in the following image versions:
+- full-sized optimized image,
+- full-sized WebP image,
+- optimized thumbnails,
+- WebP thumbnails.
+
+The optimization will also work for images included in your themes and plugins.
+
+If you want, Imagify can also display WebP images on your front-end in two ways:
+- `` tag,
+- rewrite rules in the .htaccess file.
+
+If you kept a backup copy of the original images, you have the possibility to **create their WebP version separately** (one by one or via the bulk optimization).
+
+= What our users think of Imagify? =
+
+> "Imagify is an awesome tool that is powerful & easy to use. It's fast, rivals and surpasses other established plugins/software. Awesome!" â [Simon Harper](https://twitter.com/SRHDesign/status/663758140505235456)
+>
+> "If you want to "squeeze" your images as much as possible and "trim out" your website on the highest professional level... Imagify" â [Ivica Delic](https://twitter.com/Free_LanceTools/status/685503950909476865)
+>
+> "Clearly Imagify is the most awesome WordPress plugin to compress images on your website! A must try" â [Eric Walter](https://twitter.com/EricWaltR/status/679053496382038016)
+>
+
+= Is Imagify Free? =
+
+You can optimize for free 20MB of images (about 200 images) every month. You can convert WebP for free.
+
+Need more? Have a look at our plans: [https://imagify.io/pricing](https://imagify.io/pricing)
+
+= What's next? =
+
+Have a look at our upcoming features by following our development roadmap: [https://trello.com/b/3Q8ZnSN6/imagify-roadmap](https://trello.com/b/3Q8ZnSN6/imagify-roadmap)
+
+= Who we are? =
+
+We are [WP Media](https://wp-media.me/), the startup behind WP Rocket the best caching plugin for WordPress.
+
+Our mission is to improve the web, we are making it faster with [WP Rocket](https://wp-rocket.me/) and lighter with Imagify.
+
+= Get in touch! =
+
+* Website: [Imagify.io](https://imagify.io)
+* Contact Us: [https://imagify.io/contact](https://imagify.io/contact)
+* Twitter: [https://twitter.com/imagify](https://twitter.com/imagify)
+
+= Related Plugins =
+* [WP Rocket](https://wp-rocket.me/): Best caching plugin to speed-up your WordPress website.
+* [Lazy Load](https://wordpress.org/plugins/rocket-lazy-load/): Best Lazy Load script to reduce the number of HTTP requests and improves the websites loading time.
+
+License: GPLv2 or later
+License URI: http://www.gnu.org/licenses/gpl-2.0.html
+
+== Installation ==
+
+= WordPress Admin Method =
+1. Go to you administration area in WordPress `Plugins > Add`
+2. Look for `Imagify` (use search form)
+3. Click on Install and activate the plugin
+4. Optional: find the settings page through `Settings > Imagify`
+
+= FTP Method =
+1. Upload the complete `imagify` folder to the `/wp-content/plugins/` directory
+2. Activate the plugin through the 'Plugins' menu in WordPress
+3. Optional: find the settings page through `Settings > Imagify`
+
+== Frequently Asked Questions ==
+
+= Which formats can be optimized? =
+
+Imagify can optimize JPG, PNG, PDF and GIFs (whether animated or not).
+
+=How should I know which compression level is best for me?=
+
+There are three compression levels available - Normal, Aggressive and Ultra.
+
+Normal compression is a "lossless" optimization. This means there is no loss of image quality.
+
+Aggressive and Ultra compression are more powerful, so the picture quality will be somewhat reduced. The weight of the image will be much less.
+
+We recommend Aggressive as the best balanced level that reduces the size but does not affect the quality.
+
+It would be best, however, to test the 3 levels of compression on a smaller amount of images and see how it affects the quality of your image. Once you see which one suits your needs, you can easily run the others via Bulk optimization or Media Library page.
+
+=How does the optimization process work?=
+
+The image optimization process is performed on our servers. Once done, Imagify returns the optimized image to your server. We do not edit imagesâ title or any other information, so there is nothing further to be done on your end. Your original images will be moved to a dedicated backup folder (just make sure to keep the Backup option active in Imagify settings).
+
+=How long are images stored by Imagify?=
+
+Once your images have been optimized via the WP plugin, they stay on your end forever (even if you delete the Imagify account).
+
+During the optimization process, images sent via the API or WordPress plugin are stored for one hour on our server (they are already sent back to your site and stay there safely).
+
+Using the online application, images are stored for 24 hours (with a free account) and for unlimited time if you have a paid subscription.
+
+=Can I restore images after compression?=
+
+Yes, as long as the Backup option is active in Imagify settings (it is active by default when you activate the WP Plugin).
+
+=If I remove Imagify, will my images stay compressed?=
+
+Yes, your images will stay compressed even after removing Imagify (and even after you delete your Imagify account).
+
+=If I use Imagify, do I need to continue optimizing and resizing my images with Photoshop?=
+
+Do not waste your time resizing and optimizing your images in Photoshop. Imagify takes care of everything!
+
+=Is the EXIF data of images removed?=
+
+By default EXIF data is removed. It is possible to keep it with the WordPress plugin by enabling the option in Imagify Settings page.
+
+=I used Kraken, Optimus, EWWW or WP Smush, will Imagify further optimize my images?=
+
+Absolutely. Most of the time, Imagify will still be able to optimize your images even if you have already compressed them with another tool.
+
+= Will the original images be deleted? =
+
+No. Imagify automatically replaces the images with an optimized image. The backup option allows you to keep the original images and restore them with one click.
+
+= Is it possible to re-optimize images with a different level? =
+
+Yes. By activating the backup option in the plugin, you can re-optimize each image with a different compression level.
+
+= What happens when the plugin is disabled? =
+
+When the plugin is disabled, your existing images remain optimized. Backups of the original images are still available if you have enabled the images backup option.
+
+=On which web hosts can the plugin be used?=
+
+The plugin can be used on all hosts including "managed hosting" providers like WP Engine.
+
+=Is Imagify compatible with Multi-Site?=
+
+Yes, Imagify is 100% compatible with multi-site.
+
+=Can we use Imagify on WordPress.com?=
+
+It is possible to use Imagify plugin on WordPress.com if you have a Business account.
+
+=Do you offer support?=
+
+Yes, the Imagify team offers full email support. You can contact us via [https://imagify.io/contact/](https://imagify.io/contact/).
+
+When is support available?
+
+Our support is currently available Monday-Friday 8AM-6PM CET. We answer every email so you can expect the answer from us within 24h max (unless during the weekends).
+
+=Is registration free?=
+
+Yes and no credit card is required.
+
+=Do you offer a trial version?=
+
+No. However, you get 20MB of quota per month for free.
+
+== Screenshots ==
+
+1. Bulk Optimization
+
+2. Settings Page
+
+3. Media Page
+
+4. Other Media Page
+
+== Changelog ==
+= 1.9.14 =
+* Fix: cURL not connecting to Imagify API when using PHP 8.
+* Fix: Display issue in Chrome on scrollable check groups on Imagify admin page.
+
+= 1.9.13 =
+* Improvement: Update readme with new quotas, FAQ and description info.
+
+= 1.9.12 - 2020/11/09 =
+* Improvement: Enable plugin to work with new app pricing API.
+
+= 1.9.11 - 2020/09/09 =
+* Fix: Fix settings error on multi-sites with WordPress 5.5
+* Fix: Write the correct conf file for use with webp rewrites on nginx
+* Improvement: Namespace composer dependencies to avoid possible naming collisions.
+* Security: Add blank index.php to imagify-created backup folders to disable public access.
+
+= 1.9.10 - 2020/05/26 =
+* Fix: Correctly optimize thumbnails during auto-optimization of image upload
+* Fix: Fix broken compatibility with Enable Media Replace plugin after WordPress 5.3
+
+= 1.9.9 - 2020/02/13 =
+* Fix: do not warn that all the quota has been consumed when it is not the case.
+* Fix: fix a "chunky upload" error that some users experienced.
+* Fix: php notices that could happen when optimizing.
+
+= 1.9.8.1 - 2019/11/15 =
+* Fix: webp image not showing when using the `` method and the original ` ` does not have a `srcset` attribute.
+* Fix: a fatal error with WP Offload Media 2.3.
+
+= 1.9.8 - 2019/11/11 =
+* Improvement: compatibility with WordPress 5.3!
+* New: among other things, WordPress 5.3 automatically resizes large images on upload, using a predefined threshold value that can be changed only by filter (no setting fields are provided). Imagifyâs "Resize larger images" setting field is now used to tweak this threshold.
+* Caution: to be able to work on WordPress 5.3, some adjustments have been made to our compatibility with Enable Media Replace and Regenerate Thumbnails. However, these plugins must be updated to work with WordPress 5.3: do not use them until then.
+* Improvement: moved the `width` and `height` attributes from the `` tag to the ` ` tag to be valid HTML markup.
+* Fix: added a missing descriptor in `srcset` attribute when using `` tags to display webp images. This should also fix an issue with LasyLoad.
+* Fix: fixed an issue with the user capacity used for "Other Media" menu item.
+* Fix: a php notice `stripos(): Non-string needles will be interpreted as strings in the future.`.
+
+= 1.9.7 - 2019/10/08 =
+* Improvement: prevent greedy antiviruses from crashing the website by renaming our highly dangerous php file with a ".suspected" suffix.
+* Improvement: on the settings page, display the "Save & Go to Bulk Optimizer" button only if the user has the ability to bulk optimize.
+* Fix: display the "Welcome" banner correctly when it is shown on the WP Rocketâs settings page.
+
+= 1.9.6 - 2019/07/22 =
+* Improvement: now images that are "already optimized" can also get webp versions.
+* Fix: progress bar height in the admin bar for Chrome and Safari.
+
+= 1.9.5 - 2019/07/16 =
+* Improvement: Basic Authentication support. If it does not work automatically, you can still define the constants `IMAGIFY_AUTH_USER` and `IMAGIFY_AUTH_PASSWORD` in your `wp-config.php` file.
+* Improvement: webp images are not created for animated gif images by default anymore. Use the filter `imagify_pre_can_create_webp_version` if you still want to create an unanimated webp version of them.
+* Improvement: when creating webp images from the settings page, we made more clear when all the images are missing a backup copy.
+* Improvement: clear the 5 minutes data cache when buying quota from the plugin.
+* Improvement: when displaying webp images with the `` tag, allow to use relative URLs (starting with `/`).
+
+= 1.9.4 - 2019/07/10 =
+* Improvement: if a webp image is larger than its non-webp version, it is now possible to not keep it. This can be done by using the filter `imagify_keep_large_webp`.
+* Improvement: compatibility with Pressable.
+* Improvement: renamed a php class to prevent some hosts to wrongly flag it as "suspicious" and trigger a fatal error.
+* Improvement: better compatibility with WP Real Media Library plugin.
+* Fix: rewrite rules for webp could not work on some servers.
+* Fix: when using `` tags for webp, some attributes could disappear if they were written on multiple lines.
+* Fix: the bulk method would not work in the NextGen Gallery list.
+* Fix: php notice `Trying to get property "namespace" for a non object`.
+
+= 1.9.3.1 - 2019/07/03 =
+* Fix: conflict with plugins using an ancient version of Composer.
+
+= 1.9.3 - 2019/06/17 =
+* Improvement: better compatibility with CDNs when displaying webp images with `` tags. There is now a new setting field to fill in the CDN URL in use.
+* Improvement: donât use Heartbeat anymore. This speeds up the optimization process and prevents other plugins to break everything when they remove Heartbeat.
+* Fix: a fatal error upon plugin deactivation.
+* Fix: an occasional fatal error preventing the optimization process to work.
+* Fix: conflict with plugins using an ancient version of Composer.
+* Fix: php notices displayed on the bulk optimization page on rare cases.
+* Fix: a php notice about "Non-string needles" with php 7.3.
+* Fix: a php notice displayed when restoring a custom file.
+
+= 1.9.2 - 2019/05/16 =
+* Fix: donât display support bubble anymore.
+
+= 1.9.1 - 2019/05/09 =
+* Improvement: prevent "Generating missing webp versions" being stuck at 0% in the settings page by displaying a "done/total" label instead.
+* Improvement: improved our "re-registering" of the Heartbeat library, that some plugins may deactivate.
+
+= 1.9.0 - 2019/05/06 =
+* New: webp support. For each image or thumbnail, Imagify can create a webp version of it. But since creating these images without using them does not make really sense, Imagify can also display your webp images on your site. All of this can be enabled in the settings. For the images that are already optimized, you get the possibility to create the webp versions separately (one by one or in the settings page), but only if you kept a backup copy of the original images.
+* Improvement: the optimization process has been entirely rebuilt. This new process allows you to optimize as many thumbnail sizes that you want. It also implies that many classes, functions, and hooks have been deprecated.
+* Improvement: compatibility with Flywheel.
+* Improvement: some error messages are now more accurate.
+* Fix: made sure to stop the optimization process if the backup process fails. Since the optimization process has been rebuilt, some other bugs have been fixed along the way.
+* Fix: an issue preventing directory creation.
+* Fix: a fatal error when uploading an image in NextGen Gallery, due to a recent change in NGG.
+* Imagify now requires WordPress 4.0+ and php 5.4+.
+* Support for the plugin WP Retina 2x has been dropped (maybe temporarily).
+
+= 1.8.4.1 - 2018/12/18 =
+* Improvement: prevent "unknown error" messages that some users are getting since yesterday.
+
+= 1.8.4 - 2018/11/12 =
+* Improvement: automatic optimization is delayed further, it now happens after the image original data is stored in the database. This new process should be more reliable.
+* Improvement: compatibility with wordpress.com.
+* Improvement: some wording and typos in the plan suggestion tool.
+* Improvement: improved wording and added a link to a new documentation entry for the case when no php extension are available for image manipulation.
+* Improvement: prevent plugins from accidentally overwriting the header containing the API key when contacting our servers.
+* Bug Fix: the handle in the original/optimized image comparator was a bit shy, but after some personal work it should stick to the cursor hopefully.
+* Bug Fix: a php notice in the WP Retina 2x compatibility code.
+* Bug Fix: handle a specific error case when contacting our servers fails.
+
+= 1.8.3 - 2018/10/24 =
+* Improvement: compatibility with new version of WP Offload Media plugin.
+* Improvement: some wording about EXIF Data and the 2MB limit.
+* Bug Fix: the lock icon now displays correctly.
+* Bug Fix: a text encoding issue with some server configurations.
+
+= 1.8.2 - 2018/09/12 =
+* New: display partnership links (can be removed).
+* Improvement: display a small spinner when opening a folder in the custom folders selector.
+* Improvement: visual for the admin toolbar option has been updated and localized for some languages.
+* Bug Fix: two errors that prevented to create the backup folder (and other things).
+* Bug Fix: improved uninstall cleanup.
+
+= 1.8.1.1 - 2018/07/31 =
+* Bug Fix: an open_basedir error that prevented some users to use the custom folders browser.
+* Bug Fix: an error that prevented to create the backup folder (and other things) on multisite.
+
+= 1.8.1 - 2018/07/18 =
+* Imagify now requires WordPress 4.0 at least! This value may increase in the future, like required php version.
+* Bug Fix: improved support of sites having the "wp-content" folder outside WordPress folder.
+* Bug Fix: improved the plan recommendation tool: better choices, and pre-select only what is needed.
+* Bug fix: fixed a wrong color on a quota bar.
+* Lots of various small fixes and code improvements.
+
+= 1.8.0.1 - 2018/06/19 =
+* Bug Fix: issue on some sites displaying a "no php extension available".
+
+= 1.8 - 2018/06/19 =
+* New: you can now optimize pdf files.
+* Improvement: custom folders, you can now optimize files located in the *uploads* folder.
+* Improvement: support for thumbnails dynamically generated by NextGen Gallery plugin.
+* Bug Fix: revamped support for WP Retina 2x plugin.
+
+= 1.7.1.3 - 2018/04/12 =
+* Bug Fix: a fatal error with outdated versions of php.
+
+= 1.7.1.2 - 2018/04/12 =
+* Improvement: reset OPcache after Imagify being updated.
+* Bug Fix: a fatal error upon Imagify update.
+* Bug Fix: a case where the bulk optimizer wrongly says that all images are already optimized.
+
+= 1.7.1 - 2018/04/10 =
+* New: compatibility with Regenerate Thumbnails (v3) plugin.
+* Improvement: better performance of the bulk optimization on sites with huge media library. This is done by not updating the statistics display periodically, but only when the job is done.
+* Improvement: SiteGround cache testing is not blocked anymore.
+* Improvement: proxies are now handled.
+* Improvement: test for ImageMagick or GD availability.
+* Dev stuff: improved the way we use the filesystem. This should solve few edge cases.
+
+= 1.7 - 2018/03/13 =
+* New: you can now optimize the images from your themes and plugins, or from any other folder in your site!
+* Improvement: compatibility with old and new versions of WP Offload S3 plugins.
+* Improvement: don't start the bulk optimization process if cURL is not available.
+* Bug Fix: image dimensions not being stored sometimes after it is resized.
+* Bug Fix: the comparison tool could display multiple handles.
+* Bug Fix: issue with php 7.2.
+* Dev stuff: lots of internal changes, many things have been rewritten.
+* Dev stuff: the default options can now be filtered.
+
+= 1.6.14.2 - 2018/01/15 =
+* Improvement: force browsers not to use the old version of our script for the charts.
+
+= 1.6.14.1 - 2018/01/11 =
+* Bug Fix: no more conflicts between our script used for the charts and theme builders, or plugins that use an outdated version of this script.
+
+= 1.6.14 - 2018/01/10 =
+* New: added compatibility with partners' plugins.
+* Improvement: updated the script used for the charts, it will lower the risk of conflicts with other plugins (that are also up-to-date).
+* Improvement: the comparison tool button is now also inserted when clicking the next/previous buttons in the media modal.
+* Bug Fix: the comparison tool button should not be inserted several times anymore.
+* Bug Fix: the images wouldn't appear in the comparison tool sometimes.
+
+= 1.6.13.1 - 2017/11/08 =
+* Bug Fix: fixed a php error with php 5.2.
+
+= 1.6.13 - 2017/11/07 =
+* New: added links to the documentation in Imagify' settings and bulk optimization pages.
+* Improvement: better compatibility with NextGen Gallery plugin. Imagify no longer resizes NextGen images nor removes exif, to let NextGen Gallery do its job peacefully.
+* Improvement: better compatibility with WP Real Media Library plugin, our modal wasn't working correctly.
+* Improvement: better compatibility with plugins that use cookies, like Duo Two-Factor Authentication and Shield Security, to prevent being disconnected.
+* Improvement: better compatibility with SiteGround. A "security" measure was preventing Imagify to work correctly.
+* Improvement: better compatibility with hosts that limit some SQL queries, it prevented our bulk optimization to work.
+* Improvement: better compatibility with Heartbeat Control plugin, it prevented our bulk optimization to work.
+* Improvement: better compatibility with Formidable Forms Pro plugin, the bulk optimizer was never satisfied.
+* Bug Fix: fixed a few bugs when optimizing in NextGen Gallery.
+
+= 1.6.12 - 2017/10/18 =
+* New: added links to the documentation in the plugin's admin bar item and the plugin's row (plugins page). There is more to come.
+* Improvement: image attachments that don't have some mandatory WordPress metadata are not included in Imagify stats anymore.
+* Fix: the "Optimized size" progress bar in the bulk optimization page now behaves like the "Original size" one does.
+* Dev stuff: auto-optimization can be disabled on an attachment basis with the new filter `imagify_auto_optimize_attachment`. For example it can be used to disable auto-optimization for a specific file extension.
+* Dev stuff: classes are now auto-loaded. Some constants have been removed.
+
+= 1.6.11 - 2017/10/12 =
+* Improvement: Imagify now works with the iOS app, and with XML-RPC in general.
+* Improvement: we harmonized and improved how user roles are handled.
+* Improvement: prevent optimized image to be cached by the browser in the comparison tool.
+* Fix: sometimes the comparison tool's button wouldn't show on the attachment edition page.
+* Fix: the bulk optimization button works again.
+
+= 1.6.10 - 2017/10/05 =
+* New: if new thumbnail sizes appear after activating a new theme or plugin, you can now optimize only these missing sizes instead of restoring and re-optimizing all images.
+* Improvement: CSS and JS files have been split and are loaded only when needed.
+* Improvement: in each NextGen Galleries you now have "Optimize" and "Restore" bulk actions.
+* Improvement: better banner placements with languages with long sentences (looking at you, Germany).
+* Improvement: messages like the "WELL DONE" one can now be translated.
+* Bug Fix: the account infos in the admin bar now works properly on front-end.
+* Bug Fix: some thumbnail sizes with curious name were not listed in the settings page.
+* Bug Fix: improved library size calculation for "What plan do I need?". Some thumbnail sizes were missing, lowering the result.
+* Regression fix: the issue with Imagify's popup on WP Rocket options screen is now also solved when WP Rocket is white-labelled.
+* Lots of various small fixes and code improvements.
+
+= 1.6.9.1 - 2017/08/12 =
+* Regression fix: don't load Imagify's popup files on WP Rocket options screen to avoid conflicts.
+
+= 1.6.9 - 2017/08/11 =
+* Improvement: the bulk optimization now stops as soon as the quota is fully consumed, instead of trying to optimize more images and getting error messages one after the other.
+* Improvement: updated (almost) all JavaScript libraries we use. SweetAlert won't conflict with new versions anymore. Few code improvements.
+* Improvement: in the medias list, improved the Imagify column behavior on small screens.
+* Improvement: when optimizing in NextGen Gallery, update some NGG data.
+* Bug Fix: revamped the whole Enable Media Replace plugin compatibility. Optimization, restoration, and backup should work properly now.
+* Bug Fix: revamped the way to restore images in NextGen Gallery to prevent deletion of alt text, description, and tags.
+* Regression fix: fixed optimization and restoration not working in NextGen Gallery.
+* Regression fix: fixed the bulk optimization not working with PHP 5.2.
+
+= 1.6.8 - 2017/07/26 =
+* Improvement: don't display the restore bulk action in the medias list if there is nothing to restore.
+* Improvement: you can know select and unselect all image sizes at once in the settings page.
+* Improvement: detect when the backup directory is not writable. A warning is displayed dynamically under the backup setting, a notice is also displayed on some pages.
+* Improvement: some strings were still not translated in the bulk optimization page.
+* Bug Fix: the "Save & Go to Bulk Optimizer" button now redirects you even if no settings have been changed.
+* Lots of various small fixes and code improvements.
+
+= 1.6.7.1 - 2017/07/13 =
+* Bug Fix: Fixed the "Unknown error" during a bulk optimization.
+
+= 1.6.7 - 2017/07/12 =
+* Improvement: Compatibility with the plugin WP Offload S3 Pro, and fixed a few things for both Lite and Pro versions.
+* Improvement: Improved performance on the bulk optimization page for huge image libraries.
+* Improvement: When performing a bulk optimization, moved the attachments with the "WELL DONE" message at the end of the queue, it helps to speed up things.
+* Improvement: Use cURL directly only to optimize an image. It helps when cURL is not available: less things will break in that case.
+* Bug Fix: Fixed a bug with the plugin Screets Live Chat, prior to version 2.2.8.
+* Regression fix: Fixed the buffer size on the bulk optimization page.
+* Dev stuff: Added a hook allowing to filter arguments when doing a request to our API. It can be used to increase the timeout value for example.
+
+= 1.6.6 - 2017/06/27 =
+* New: Compatibility with the plugin WP Offload S3 Lite. Your images now will be sent to Amazon S3 after being optimized. Also works when you store your images only on S3, not locally.
+* Improvement: Added a filter to the asynchronous job arguments.
+* Bug fix: Compatibility with Internet Explorer 9 to 11.
+* Regression fix: The comparison tool stopped working in the medias list since the previous version.
+
+= 1.6.5 - 2017/06/22 =
+* Improvement: Code quality of the whole plugin has been improved to fit more WordPress coding standards.
+* Improvement: Lots of internationalization improvements. Now the plugin's internationalization fully rely on the repository system.
+* Bug Fix: Fixed an error with php 7.1: `Uncaught Error: [] operator not supported for strings in /wp-content/plugins/imagify/inc/functions/admin.php:134`.
+
+= 1.6.4 - 2017/04/06 =
+* Improvement: Provide a link to optimize in higher level when an image is already optimized.
+* Improvement: Add a dedicated message for 413 HTTP error when the image is too big to be uploaded on our servers.
+
+= 1.6.3 - 2016/12/16 =
+* Improvement: The discount is now automatically applied in when you buy from the plugin and a promotion is active
+
+= 1.6.2 - 2016/11/22 =
+* Bug Fix: Correctly display the modal when clicking on the plan suggestion button on bulk optimization page
+
+= 1.6.1 - 2016/11/22 =
+* Bug Fix: Better offer suggestion when your medias library is bigger than 3GB
+
+= 1.6 - 2016/11/21 =
+* New: Knowing how many MB/GB you need to optimize your existing and future images is complicated. We love to make things easier, so Imagify will do it and advise you the best plan.
+* New: You can now buy all the plans without leaving your WordPress administration
+* Improvement: Some styles fixed in the interface
+
+= 1.5.10 - 2016/10/05 =
+* Improvement: Set to 1 the Bulk buffer size when there are more than 10 thumbnails to avoid "Unknown error" on the Bulk Optimization
+
+= 1.5.9 - 2016/09/27 =
+* Bug Fix: Don't delete the thumbnail when the maximum file size is set to one of the thumbnail size
+* Bug Fix: Don't strip the image meta data if possible (only with Imagick)
+* Bug Fix: Fix persistent "WELL DONE" message because of "original_size" meta value was 0
+
+= 1.5.8 - 2016/08/24 =
+* Regression fix: Check if the backup option is active before doing a backup when an image is resized
+
+= 1.5.7 - 2016/08/23 =
+* Improvement: Resize images bigger than the maximum width defined in the settings using WP Image Editor instead of Imagify API
+
+= 1.5.6 - 2016/07/29 =
+* Improvement: Dynamically update from the API the maximum image size allowed in bulk optimization
+* Improvement: Updated SweetAlert to SweetAlert2
+
+= 1.5.5 =
+* Bug Fix: Fix issue with "original_size" at 0 in "_imagify_data" to be able to re-optimize an image with a "Forbidden" error.
+
+= 1.5.4 =
+* Improvement: Increase to 4 the number of parallel queries during a bulk optimization
+* Improvement: Don't display Intercom chat if the user turned off the option in the web app
+
+= 1.5.3 =
+* Regression Fix: Display the Original File size in "View Details" section
+
+= 1.5.2.1 =
+* Bug Fix: Fix JS error: Uncaught ReferenceError: imagify is not defined in /assets/options.min.js
+* Bug Fix: Don't show "Optimize" button during optimizing process in "Edit Media" screen
+
+= 1.5.1 =
+* Bug Fix: Thumbnail sizes in settings page aren't reset anymore on plugin update
+* Bug Fix: Fix PHP Warning: Cannot unset offset in a non-array variable in /inc/functions/admin-stats.php on line 23
+* Bug Fix: Fix PHP Warning: Invalid argument supplied for foreach() in /inc/functions/admin-stats.php on line 233
+
+= 1.5 =
+* New: NextGen Gallery compatibility - Optimize all your images uploaded with NextGen Gallery
+* New: Asynchronous Optimization - No more latency when you upload new images, Imagify will optimize them in background!
+* Improvement: Bulk Optimization: Interface improvements for a better experience
+
+= 1.4.7 =
+* Bug Fix: Fix issue between Bulk Optimization & WP Engine. The query to get unoptimized images is limited to 2500 images to be able to use the Bulk Optimization on this hosting.
+* Bug Fix: Fix SSL certificate problem: unable to get local issuer certificate
+
+= 1.4.6 =
+* Bug Fix: Fix the "All your images have been optimized by Imagify" issue when images still need to be optimized. This issue occurred only since 1.4.5 for some users. Sorry for the inconvenience!
+
+= 1.4.5 =
+* Improvement: Bulk Optimization: optimize all SQL queries and improve by 65% the process time \o/
+* Improvement: Chart.js library updated
+* Improvement: Media List JS notice removed
+
+= 1.4.4 =
+* Improvement: Visual fix: CSS prefixed in notices to avoid class conflicts
+* Improvement: Visual fix: improve Imagify Notices CSS to avoid issue with WP Engine CSS
+* Improvement: Medias: new "Compare Original VS Optimized" action link in grid view mode
+* Improvement: Settings: new sample images for visual comparison of compression levels (removes unused sample images)
+
+= 1.4.3 =
+* New: Medias: new "Compare Original VS Optimized" action link in list view
+* Improvement: Visual fix: CSS prefixed in notices to avoid class conflicts
+* Improvement: Medias: comparison are now available for image from 36Opx wide
+* Improvement: Settings: new sample images for visual comparison of compression levels
+
+= 1.4.2 =
+* New: Add German translation
+* New: You can define the `IMAGIFY_HIDDEN_ACCOUNT` constant in wp-config.php to hide all your Imagify account infos in the Admin Bar and Bulk Optimization
+* Bug Fix: Fix PHP Notice: Undefined index original_size in /inc/functions/admin-stats.php on line 185
+* Bug Fix: Fix PHP Notice: Undefined index optimized_size in /inc/functions/admin-stats.php on line 186
+
+= 1.4.1 =
+* Improvement: Medias: better comparison for big portrait images
+* Improvement: Medias: Don't display the "Compare Original VS Optimized" button for images without backup
+* Bug Fix: WPML: Fix AJAX error caused by WPML to avoid issue during the API key validation process
+* Bug Fix: Yoast: Remove JS error caused by Yoast SEO on the attachment edit screen to avoid issue with our "Compare Original VS Optimized"
+
+= 1.4 =
+* New: Medias: Click a button to open images comparison between Original and Optimized (available for big enough images)
+* Improvement: Add async method to optimize resized images
+
+= 1.3.6 =
+* Improvement: Optimize attachments resized with the WordPress editor tool
+* Improvement: Compatibility with the "Replace the file, use new file name and update all links" option from "Enable Media Replace" plugin
+* Improvement: Add a notice message during the Bulk Optimization if the quota is consumed
+* Improvement: Better styles for compression details next to your images
+* Bug Fix: No freeze anymore during the Bulk Optimization if an unknown error occurred with an image
+* Bug Fix: Add a notice message if we can't get all unoptimized images during the Bulk Optimization process
+* Bug Fix: Fix PHP Warning: set_time_limit(): Cannot set time limit in safe mode in ../inc/admin/ajax.php on line 137
+* Bug Fix: Details about compressed images in modal media box are now closed by default
+* Regression Fix: Get all attachments with the message "You've consumed all your data" during the Bulk Optimization process to be able to optimize them
+
+= 1.3.5.2 =
+* Regression Fix: Check mark displayed better on certain settings pages
+
+= 1.3.5 =
+* Bug Fix: Check box display issue fixed on Imagify settings page: SVG Icons cleaning
+
+= 1.3.4 =
+* New: Add Italian translation
+
+= 1.3.3 =
+* Bug Fix: Fixed behavior in multisite networks where Imagify options would not get saved when the plugin wasn't network-activated, but only activated for specific sites within the network.
+
+= 1.3.2 =
+* New: Add Spanish translation
+* Bug Fix: Avoid lack of performance in the WordPress administration if the Imagify's servers are down.
+
+= 1.3.1 =
+* Bug Fix: Remove a notice message which causes a lack of performance in the administration. (thanks Kevin Gauthier to warn us)
+
+= 1.3 =
+* New: Add GIF support
+* New: You can now decide to keep EXIF data on your images
+
+= 1.2.4 =
+* Bug Fix: Don't duplicate Imagify data in the attachment edit screen (wp-admin/post.php)
+
+= 1.2.3 =
+* Improvement: Use AJAX to display the quota in the admin bar to avoid a call to our API on each pages.
+
+= 1.2.2 =
+* Bug Fix: Bulk Optimization: Fix issue when the backup option isn't activated. The compression level applied was "Normal" instead the one saved in the settings.
+* Bug Fix: Bulk Optimization: Don't try to re-optimize an image already optimized which has the same compression level than the one saved in the settings.
+
+= 1.2.1 =
+* Regression fix: Fix the Bulk Optimization issue when you never optimized any images and avoid the message "All your images have been optimized by Imagify. Congratulations!".
+
+= 1.2 =
+* New: compression level: Ultra
+* New: You can now choose to display Admin Bar Imagify's menu, or not.
+* New: See the differences between Ultra, Aggressive and Normal option inside Imagify Options page.
+* Bug Fix: Admin Bar: Styles are now included in front-end too.
+* Bug Fix: Admin Bar: Better styles in certain cases.
+* Bug Fix: Deactivate a conflict plugin doesn't return a blank page anymore!
+* Bug Fix: Display the right original image size after a resize (meta data)
+* Regression Fix: Bulk Optimization: update in live the unconsumed credit during a bulk optimization.
+
+= 1.1.6 =
+* Improvement: Quick access to your profile informations (quota) in Admin Bar > Imagify
+* Improvement: More precise information about global size saved using Imagify (bulk optimization page)
+* Improvement: When your bulk optimization is over, success message isn't inside the table anymore
+* Improvement: To quit the bulk optimization processing you have to confirm your action
+* Bug Fix: JS: `console` undefined on some IE browsers
+* Bug Fix: PHP Warning: `Illegal string offset 'sizes' in ../inc/functions/admin-stats.php on line 180`
+* Bug Fix: Don't count GIF & SVG in the Imagify statistics
+
+= 1.1.5 =
+* Improvement: Display a default preview to avoid issues with 404 images and a security restriction on SSL websites on the Bulk Optimization page
+* Improvement: Don't count all exceeded images to avoid lack of speed on the Bulk Optimization page
+* Bug Fix: Don't try to re-optimize images with an empty error message or with an already optimized message on the Bulk Optimization
+* Bug Fix: Don't generate special chars in the password to avoid issue on the Imagify app log in
+
+= 1.1.4 =
+* Improvement: Don't add the WP Rocket ads if this plugin is activated
+* Bug Fix: Ignore thumbnails with infinite width like 9999 to avoid an issue with the "Resize larger images" option
+
+= 1.1.3 =
+* Bug Fix: Fix PHP Warning: `curl_setopt() [function.curl-setopt]: CURLOPT_FOLLOWLOCATION cannot be activated when safe_mode is enabled or an open_basedir is set in ../inc/api/imagify.php on line 218`
+
+= 1.1.2 =
+* Regression fix: Fix the "%undefined%" and the overview chart issues on the Bulk Optimization page
+* Regression fix: Fix PHP Warning: Illegal string offset 'sizes' in ../inc/classes/class-attachment.php on line 347
+* Regression fix: Fix PHP Notice: Uninitialized string offset: 0 in ../inc/classes/class-attachment.php on line 347
+* Regression fix: Fix PHP Warning: Illegal string offset 'file' in ../inc/classes/class-attachment.php on line 410
+
+= 1.1.1 =
+* New: Add a notice on the Bulk Optimization & Imagify Settings page when the monthly free quota is consumed
+* Bug Fix: Fix issue on Chrome & Opera on the Bulk Optimization: images are optimized from the newest to the oldest.
+
+= 1.1 =
+* New: Add new option "Resize larger Images"
+* Improvement: Bulk optimization: results table is not shrinkable to the infinite anymore (scrollable)
+* Improvement: Better visual in options page
+* Bug Fix: Check if an attachment exists to avoid an issue which is stopped the Bulk Optimization
+* Bug Fix: Really Fix PHP Notice: Undefined offset: 1 in imagify/inc/functions/formatting.php on line 17
+* Bug Fix: Double animation in Progress Bar
+
+= 1.0.3 =
+* Bug Fix: Fix PHP Notice: Undefined offset: 1 in ../inc/functions/formatting.php on line 16
+
+= 1.0.2 =
+* Improvement: Add error descriptions on the Bulk Optimization results
+* Improvement: Add a notice to switch to the list view in the media library page
+
+= 1.0.1 =
+* New: Add Intercom Live Chat on Imagify Settings and Bulk Optimization pages
+* Improvement: Better user informations
+* Bug Fix: PHP 5.2+ compatibility
+
+= 1.0 =
+* Initial release.
diff --git a/wp-content/plugins/imagify/uninstall.php b/wp-content/plugins/imagify/uninstall.php
new file mode 100644
index 00000000..578392ad
--- /dev/null
+++ b/wp-content/plugins/imagify/uninstall.php
@@ -0,0 +1,50 @@
+prefix . 'ngg_imagify_data_db_version' );
+
+// Delete all transients.
+delete_site_transient( 'imagify_activation' );
+delete_site_transient( 'imagify_check_licence_1' );
+delete_site_transient( 'imagify_user' );
+delete_site_transient( 'imagify_themes_plugins_to_sync' );
+delete_site_transient( 'do_imagify_rating_cron' );
+delete_site_transient( 'imagify_seen_rating_notice' );
+delete_site_transient( 'imagify_user_images_count' );
+delete_transient( 'imagify_activation' );
+delete_transient( 'imagify_bulk_optimization_level' );
+delete_transient( 'imagify_bulk_optimization_infos' );
+delete_transient( 'imagify_large_library' );
+delete_transient( 'imagify_max_image_size' );
+delete_transient( 'imagify_user' );
+delete_transient( 'imagify_stat_without_webp' );
+
+// Delete transients.
+$transients = implode( '" OR option_name LIKE "', array(
+ '\_transient\_%imagify-auto-optimize-%',
+ '\_transient\_%imagify\_rpc\_%',
+ '\_transient\_imagify\_%\_process\_locked',
+ '\_site\_transient\_imagify\_%\_process\_lock%',
+) );
+$wpdb->query( "DELETE from $wpdb->options WHERE option_name LIKE \"$transients\"" ); // WPCS: unprepared SQL ok.
+
+// Clear scheduled hooks.
+wp_clear_scheduled_hook( 'imagify_rating_event' );
+wp_clear_scheduled_hook( 'imagify_update_library_size_calculations_event' );
+
+// Delete all user meta related to Imagify.
+delete_metadata( 'user', '', '_imagify_ignore_notices', '', true );
+
+// Drop the tables.
+$wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'imagify_files' );
+$wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'imagify_folders' );
+$wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'ngg_imagify_data' );
diff --git a/wp-content/plugins/imagify/vendor/autoload.php b/wp-content/plugins/imagify/vendor/autoload.php
new file mode 100644
index 00000000..6a2e6560
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/autoload.php
@@ -0,0 +1,7 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\AutoloadWPMediaImagifyWordPressPlugin;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\AutoloadWPMediaImagifyWordPressPlugin\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/LICENSE b/wp-content/plugins/imagify/vendor/composer/LICENSE
new file mode 100644
index 00000000..f27399a0
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/wp-content/plugins/imagify/vendor/composer/autoload_classmap.php b/wp-content/plugins/imagify/vendor/composer/autoload_classmap.php
new file mode 100644
index 00000000..848f55c3
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/autoload_classmap.php
@@ -0,0 +1,221 @@
+ $vendorDir . '/composer/installers/src/Composer/Installers/AglInstaller.php',
+ 'Composer\\Installers\\AimeosInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AimeosInstaller.php',
+ 'Composer\\Installers\\AnnotateCmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php',
+ 'Composer\\Installers\\AsgardInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AsgardInstaller.php',
+ 'Composer\\Installers\\AttogramInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/AttogramInstaller.php',
+ 'Composer\\Installers\\BaseInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/BaseInstaller.php',
+ 'Composer\\Installers\\BitrixInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/BitrixInstaller.php',
+ 'Composer\\Installers\\BonefishInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/BonefishInstaller.php',
+ 'Composer\\Installers\\CakePHPInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CakePHPInstaller.php',
+ 'Composer\\Installers\\ChefInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ChefInstaller.php',
+ 'Composer\\Installers\\CiviCrmInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CiviCrmInstaller.php',
+ 'Composer\\Installers\\ClanCatsFrameworkInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php',
+ 'Composer\\Installers\\CockpitInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CockpitInstaller.php',
+ 'Composer\\Installers\\CodeIgniterInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php',
+ 'Composer\\Installers\\Concrete5Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Concrete5Installer.php',
+ 'Composer\\Installers\\CraftInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CraftInstaller.php',
+ 'Composer\\Installers\\CroogoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/CroogoInstaller.php',
+ 'Composer\\Installers\\DecibelInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DecibelInstaller.php',
+ 'Composer\\Installers\\DframeInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DframeInstaller.php',
+ 'Composer\\Installers\\DokuWikiInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DokuWikiInstaller.php',
+ 'Composer\\Installers\\DolibarrInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DolibarrInstaller.php',
+ 'Composer\\Installers\\DrupalInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/DrupalInstaller.php',
+ 'Composer\\Installers\\ElggInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ElggInstaller.php',
+ 'Composer\\Installers\\EliasisInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/EliasisInstaller.php',
+ 'Composer\\Installers\\ExpressionEngineInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php',
+ 'Composer\\Installers\\EzPlatformInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/EzPlatformInstaller.php',
+ 'Composer\\Installers\\FuelInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/FuelInstaller.php',
+ 'Composer\\Installers\\FuelphpInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/FuelphpInstaller.php',
+ 'Composer\\Installers\\GravInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/GravInstaller.php',
+ 'Composer\\Installers\\HuradInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/HuradInstaller.php',
+ 'Composer\\Installers\\ImageCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ImageCMSInstaller.php',
+ 'Composer\\Installers\\Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Installer.php',
+ 'Composer\\Installers\\ItopInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ItopInstaller.php',
+ 'Composer\\Installers\\JoomlaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/JoomlaInstaller.php',
+ 'Composer\\Installers\\KanboardInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KanboardInstaller.php',
+ 'Composer\\Installers\\KirbyInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KirbyInstaller.php',
+ 'Composer\\Installers\\KnownInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KnownInstaller.php',
+ 'Composer\\Installers\\KodiCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KodiCMSInstaller.php',
+ 'Composer\\Installers\\KohanaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/KohanaInstaller.php',
+ 'Composer\\Installers\\LanManagementSystemInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php',
+ 'Composer\\Installers\\LaravelInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/LaravelInstaller.php',
+ 'Composer\\Installers\\LavaLiteInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/LavaLiteInstaller.php',
+ 'Composer\\Installers\\LithiumInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/LithiumInstaller.php',
+ 'Composer\\Installers\\MODULEWorkInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php',
+ 'Composer\\Installers\\MODXEvoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MODXEvoInstaller.php',
+ 'Composer\\Installers\\MagentoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MagentoInstaller.php',
+ 'Composer\\Installers\\MajimaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MajimaInstaller.php',
+ 'Composer\\Installers\\MakoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MakoInstaller.php',
+ 'Composer\\Installers\\MantisBTInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MantisBTInstaller.php',
+ 'Composer\\Installers\\MauticInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MauticInstaller.php',
+ 'Composer\\Installers\\MayaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MayaInstaller.php',
+ 'Composer\\Installers\\MediaWikiInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MediaWikiInstaller.php',
+ 'Composer\\Installers\\MicroweberInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MicroweberInstaller.php',
+ 'Composer\\Installers\\ModxInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ModxInstaller.php',
+ 'Composer\\Installers\\MoodleInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/MoodleInstaller.php',
+ 'Composer\\Installers\\OctoberInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/OctoberInstaller.php',
+ 'Composer\\Installers\\OntoWikiInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/OntoWikiInstaller.php',
+ 'Composer\\Installers\\OsclassInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/OsclassInstaller.php',
+ 'Composer\\Installers\\OxidInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/OxidInstaller.php',
+ 'Composer\\Installers\\PPIInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PPIInstaller.php',
+ 'Composer\\Installers\\PhiftyInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PhiftyInstaller.php',
+ 'Composer\\Installers\\PhpBBInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PhpBBInstaller.php',
+ 'Composer\\Installers\\PimcoreInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PimcoreInstaller.php',
+ 'Composer\\Installers\\PiwikInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PiwikInstaller.php',
+ 'Composer\\Installers\\PlentymarketsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php',
+ 'Composer\\Installers\\Plugin' => $vendorDir . '/composer/installers/src/Composer/Installers/Plugin.php',
+ 'Composer\\Installers\\PortoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PortoInstaller.php',
+ 'Composer\\Installers\\PrestashopInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PrestashopInstaller.php',
+ 'Composer\\Installers\\ProcessWireInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ProcessWireInstaller.php',
+ 'Composer\\Installers\\PuppetInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PuppetInstaller.php',
+ 'Composer\\Installers\\PxcmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/PxcmsInstaller.php',
+ 'Composer\\Installers\\RadPHPInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/RadPHPInstaller.php',
+ 'Composer\\Installers\\ReIndexInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ReIndexInstaller.php',
+ 'Composer\\Installers\\Redaxo5Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Redaxo5Installer.php',
+ 'Composer\\Installers\\RedaxoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/RedaxoInstaller.php',
+ 'Composer\\Installers\\RoundcubeInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/RoundcubeInstaller.php',
+ 'Composer\\Installers\\SMFInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SMFInstaller.php',
+ 'Composer\\Installers\\ShopwareInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ShopwareInstaller.php',
+ 'Composer\\Installers\\SilverStripeInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SilverStripeInstaller.php',
+ 'Composer\\Installers\\SiteDirectInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SiteDirectInstaller.php',
+ 'Composer\\Installers\\StarbugInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/StarbugInstaller.php',
+ 'Composer\\Installers\\SyDESInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SyDESInstaller.php',
+ 'Composer\\Installers\\SyliusInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/SyliusInstaller.php',
+ 'Composer\\Installers\\Symfony1Installer' => $vendorDir . '/composer/installers/src/Composer/Installers/Symfony1Installer.php',
+ 'Composer\\Installers\\TYPO3CmsInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php',
+ 'Composer\\Installers\\TYPO3FlowInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php',
+ 'Composer\\Installers\\TaoInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TaoInstaller.php',
+ 'Composer\\Installers\\TheliaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TheliaInstaller.php',
+ 'Composer\\Installers\\TuskInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/TuskInstaller.php',
+ 'Composer\\Installers\\UserFrostingInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/UserFrostingInstaller.php',
+ 'Composer\\Installers\\VanillaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/VanillaInstaller.php',
+ 'Composer\\Installers\\VgmcpInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/VgmcpInstaller.php',
+ 'Composer\\Installers\\WHMCSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WHMCSInstaller.php',
+ 'Composer\\Installers\\WolfCMSInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WolfCMSInstaller.php',
+ 'Composer\\Installers\\WordPressInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/WordPressInstaller.php',
+ 'Composer\\Installers\\YawikInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/YawikInstaller.php',
+ 'Composer\\Installers\\ZendInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ZendInstaller.php',
+ 'Composer\\Installers\\ZikulaInstaller' => $vendorDir . '/composer/installers/src/Composer/Installers/ZikulaInstaller.php',
+ 'Dangoodman\\ComposerForWordpress\\ComposerForWordpress' => $vendorDir . '/dangoodman/composer-for-wordpress/ComposerForWordpress.php',
+ 'Imagify' => $baseDir . '/inc/classes/class-imagify.php',
+ 'Imagify\\Auth\\Basic' => $baseDir . '/classes/Auth/Basic.php',
+ 'Imagify\\Bulk\\AbstractBulk' => $baseDir . '/classes/Bulk/AbstractBulk.php',
+ 'Imagify\\Bulk\\BulkInterface' => $baseDir . '/classes/Bulk/BulkInterface.php',
+ 'Imagify\\Bulk\\CustomFolders' => $baseDir . '/classes/Bulk/CustomFolders.php',
+ 'Imagify\\Bulk\\Noop' => $baseDir . '/classes/Bulk/Noop.php',
+ 'Imagify\\Bulk\\WP' => $baseDir . '/classes/Bulk/WP.php',
+ 'Imagify\\CDN\\PushCDNInterface' => $baseDir . '/classes/CDN/PushCDNInterface.php',
+ 'Imagify\\Context\\AbstractContext' => $baseDir . '/classes/Context/AbstractContext.php',
+ 'Imagify\\Context\\ContextInterface' => $baseDir . '/classes/Context/ContextInterface.php',
+ 'Imagify\\Context\\CustomFolders' => $baseDir . '/classes/Context/CustomFolders.php',
+ 'Imagify\\Context\\Noop' => $baseDir . '/classes/Context/Noop.php',
+ 'Imagify\\Context\\WP' => $baseDir . '/classes/Context/WP.php',
+ 'Imagify\\DB\\DBInterface' => $baseDir . '/classes/DB/DBInterface.php',
+ 'Imagify\\Deprecated\\Traits\\Media\\CustomFoldersDeprecatedTrait' => $baseDir . '/inc/deprecated/Traits/Media/CustomFoldersDeprecatedTrait.php',
+ 'Imagify\\Deprecated\\Traits\\Media\\NGGDeprecatedTrait' => $baseDir . '/inc/deprecated/Traits/Media/NGGDeprecatedTrait.php',
+ 'Imagify\\Deprecated\\Traits\\Media\\NoopDeprecatedTrait' => $baseDir . '/inc/deprecated/Traits/Media/NoopDeprecatedTrait.php',
+ 'Imagify\\Deprecated\\Traits\\Media\\WPDeprecatedTrait' => $baseDir . '/inc/deprecated/Traits/Media/WPDeprecatedTrait.php',
+ 'Imagify\\Deprecated\\Traits\\Optimization\\Process\\AbstractProcessDeprecatedTrait' => $baseDir . '/inc/deprecated/Traits/Optimization/Process/AbstractProcessDeprecatedTrait.php',
+ 'Imagify\\Imagifybeat\\Actions' => $baseDir . '/classes/Imagifybeat/Actions.php',
+ 'Imagify\\Imagifybeat\\Core' => $baseDir . '/classes/Imagifybeat/Core.php',
+ 'Imagify\\Job\\MediaOptimization' => $baseDir . '/classes/Job/MediaOptimization.php',
+ 'Imagify\\Media\\AbstractMedia' => $baseDir . '/classes/Media/AbstractMedia.php',
+ 'Imagify\\Media\\CustomFolders' => $baseDir . '/classes/Media/CustomFolders.php',
+ 'Imagify\\Media\\MediaInterface' => $baseDir . '/classes/Media/MediaInterface.php',
+ 'Imagify\\Media\\Noop' => $baseDir . '/classes/Media/Noop.php',
+ 'Imagify\\Media\\WP' => $baseDir . '/classes/Media/WP.php',
+ 'Imagify\\Optimization\\Data\\AbstractData' => $baseDir . '/classes/Optimization/Data/AbstractData.php',
+ 'Imagify\\Optimization\\Data\\CustomFolders' => $baseDir . '/classes/Optimization/Data/CustomFolders.php',
+ 'Imagify\\Optimization\\Data\\DataInterface' => $baseDir . '/classes/Optimization/Data/DataInterface.php',
+ 'Imagify\\Optimization\\Data\\Noop' => $baseDir . '/classes/Optimization/Data/Noop.php',
+ 'Imagify\\Optimization\\Data\\WP' => $baseDir . '/classes/Optimization/Data/WP.php',
+ 'Imagify\\Optimization\\File' => $baseDir . '/classes/Optimization/File.php',
+ 'Imagify\\Optimization\\Process\\AbstractProcess' => $baseDir . '/classes/Optimization/Process/AbstractProcess.php',
+ 'Imagify\\Optimization\\Process\\CustomFolders' => $baseDir . '/classes/Optimization/Process/CustomFolders.php',
+ 'Imagify\\Optimization\\Process\\Noop' => $baseDir . '/classes/Optimization/Process/Noop.php',
+ 'Imagify\\Optimization\\Process\\ProcessInterface' => $baseDir . '/classes/Optimization/Process/ProcessInterface.php',
+ 'Imagify\\Optimization\\Process\\WP' => $baseDir . '/classes/Optimization/Process/WP.php',
+ 'Imagify\\Stats\\OptimizedMediaWithoutWebp' => $baseDir . '/classes/Stats/OptimizedMediaWithoutWebp.php',
+ 'Imagify\\Stats\\StatInterface' => $baseDir . '/classes/Stats/StatInterface.php',
+ 'Imagify\\ThirdParty\\AS3CF\\CDN\\WP\\AS3' => $baseDir . '/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php',
+ 'Imagify\\ThirdParty\\AS3CF\\Main' => $baseDir . '/inc/3rd-party/amazon-s3-and-cloudfront/classes/Main.php',
+ 'Imagify\\ThirdParty\\EnableMediaReplace\\Main' => $baseDir . '/inc/3rd-party/enable-media-replace/classes/Main.php',
+ 'Imagify\\ThirdParty\\FormidablePro\\Main' => $baseDir . '/inc/3rd-party/formidable-pro/classes/Main.php',
+ 'Imagify\\ThirdParty\\NGG\\Bulk\\NGG' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/Bulk/NGG.php',
+ 'Imagify\\ThirdParty\\NGG\\Context\\NGG' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/Context/NGG.php',
+ 'Imagify\\ThirdParty\\NGG\\DB' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/DB.php',
+ 'Imagify\\ThirdParty\\NGG\\DynamicThumbnails' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/DynamicThumbnails.php',
+ 'Imagify\\ThirdParty\\NGG\\Main' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/Main.php',
+ 'Imagify\\ThirdParty\\NGG\\Media\\NGG' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php',
+ 'Imagify\\ThirdParty\\NGG\\NGGStorage' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/NGGStorage.php',
+ 'Imagify\\ThirdParty\\NGG\\Optimization\\Data\\NGG' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php',
+ 'Imagify\\ThirdParty\\NGG\\Optimization\\Process\\NGG' => $baseDir . '/inc/3rd-party/nextgen-gallery/classes/Optimization/Process/NGG.php',
+ 'Imagify\\ThirdParty\\RegenerateThumbnails\\Main' => $baseDir . '/inc/3rd-party/regenerate-thumbnails/classes/Main.php',
+ 'Imagify\\ThirdParty\\WPRocket\\Main' => $baseDir . '/inc/3rd-party/wp-rocket/classes/Main.php',
+ 'Imagify\\Traits\\InstanceGetterTrait' => $baseDir . '/classes/Traits/InstanceGetterTrait.php',
+ 'Imagify\\Traits\\MediaRowTrait' => $baseDir . '/classes/Traits/MediaRowTrait.php',
+ 'Imagify\\Webp\\Apache' => $baseDir . '/classes/Webp/Apache.php',
+ 'Imagify\\Webp\\Display' => $baseDir . '/classes/Webp/Display.php',
+ 'Imagify\\Webp\\IIS' => $baseDir . '/classes/Webp/IIS.php',
+ 'Imagify\\Webp\\Picture\\Display' => $baseDir . '/classes/Webp/Picture/Display.php',
+ 'Imagify\\Webp\\RewriteRules\\Apache' => $baseDir . '/classes/Webp/RewriteRules/Apache.php',
+ 'Imagify\\Webp\\RewriteRules\\Display' => $baseDir . '/classes/Webp/RewriteRules/Display.php',
+ 'Imagify\\Webp\\RewriteRules\\IIS' => $baseDir . '/classes/Webp/RewriteRules/IIS.php',
+ 'Imagify\\Webp\\RewriteRules\\Nginx' => $baseDir . '/classes/Webp/RewriteRules/Nginx.php',
+ 'Imagify\\WriteFile\\AbstractApacheDirConfFile' => $baseDir . '/classes/WriteFile/AbstractApacheDirConfFile.php',
+ 'Imagify\\WriteFile\\AbstractIISDirConfFile' => $baseDir . '/classes/WriteFile/AbstractIISDirConfFile.php',
+ 'Imagify\\WriteFile\\AbstractNginxDirConfFile' => $baseDir . '/classes/WriteFile/AbstractNginxDirConfFile.php',
+ 'Imagify\\WriteFile\\AbstractWriteDirConfFile' => $baseDir . '/classes/WriteFile/AbstractWriteDirConfFile.php',
+ 'Imagify\\WriteFile\\WriteFileInterface' => $baseDir . '/classes/WriteFile/WriteFileInterface.php',
+ 'Imagify_AS3CF_Attachment' => $baseDir . '/inc/deprecated/classes/class-imagify-as3cf-attachment.php',
+ 'Imagify_AS3CF_Deprecated' => $baseDir . '/inc/deprecated/classes/class-imagify-as3cf-deprecated.php',
+ 'Imagify_Abstract_Attachment' => $baseDir . '/inc/deprecated/classes/class-imagify-abstract-attachment.php',
+ 'Imagify_Abstract_Background_Process' => $baseDir . '/inc/classes/class-imagify-abstract-background-process.php',
+ 'Imagify_Abstract_Cron' => $baseDir . '/inc/classes/class-imagify-abstract-cron.php',
+ 'Imagify_Abstract_DB' => $baseDir . '/inc/classes/class-imagify-abstract-db.php',
+ 'Imagify_Abstract_DB_Deprecated' => $baseDir . '/inc/deprecated/classes/class-imagify-abstract-db-deprecated.php',
+ 'Imagify_Abstract_Options' => $baseDir . '/inc/classes/class-imagify-abstract-options.php',
+ 'Imagify_Admin_Ajax_Post' => $baseDir . '/inc/classes/class-imagify-admin-ajax-post.php',
+ 'Imagify_Admin_Ajax_Post_Deprecated' => $baseDir . '/inc/deprecated/classes/class-imagify-admin-ajax-post-deprecated.php',
+ 'Imagify_Assets' => $baseDir . '/inc/classes/class-imagify-assets.php',
+ 'Imagify_Assets_Deprecated' => $baseDir . '/inc/deprecated/classes/class-imagify-assets-deprecated.php',
+ 'Imagify_Attachment' => $baseDir . '/inc/deprecated/classes/class-imagify-attachment.php',
+ 'Imagify_Auto_Optimization' => $baseDir . '/inc/classes/class-imagify-auto-optimization.php',
+ 'Imagify_Auto_Optimization_Deprecated' => $baseDir . '/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php',
+ 'Imagify_Cron_Library_Size' => $baseDir . '/inc/classes/class-imagify-cron-library-size.php',
+ 'Imagify_Cron_Rating' => $baseDir . '/inc/classes/class-imagify-cron-rating.php',
+ 'Imagify_Cron_Sync_Files' => $baseDir . '/inc/classes/class-imagify-cron-sync-files.php',
+ 'Imagify_Custom_Folders' => $baseDir . '/inc/classes/class-imagify-custom-folders.php',
+ 'Imagify_DB' => $baseDir . '/inc/classes/class-imagify-db.php',
+ 'Imagify_Data' => $baseDir . '/inc/classes/class-imagify-data.php',
+ 'Imagify_Enable_Media_Replace_Deprecated' => $baseDir . '/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php',
+ 'Imagify_File_Attachment' => $baseDir . '/inc/deprecated/classes/class-imagify-file-attachment.php',
+ 'Imagify_Files_DB' => $baseDir . '/inc/classes/class-imagify-files-db.php',
+ 'Imagify_Files_Iterator' => $baseDir . '/inc/classes/class-imagify-files-iterator.php',
+ 'Imagify_Files_List_Table' => $baseDir . '/inc/classes/class-imagify-files-list-table.php',
+ 'Imagify_Files_Recursive_Iterator' => $baseDir . '/inc/classes/class-imagify-files-recursive-iterator.php',
+ 'Imagify_Files_Scan' => $baseDir . '/inc/classes/class-imagify-files-scan.php',
+ 'Imagify_Files_Stats' => $baseDir . '/inc/classes/class-imagify-files-stats.php',
+ 'Imagify_Filesystem' => $baseDir . '/inc/classes/class-imagify-filesystem.php',
+ 'Imagify_Folders_DB' => $baseDir . '/inc/classes/class-imagify-folders-db.php',
+ 'Imagify_NGG_Attachment' => $baseDir . '/inc/deprecated/classes/class-imagify-ngg-attachment.php',
+ 'Imagify_NGG_Dynamic_Thumbnails_Background_Process' => $baseDir . '/inc/deprecated/classes/class-imagify-ngg-dynamic-thumbnails-background-process.php',
+ 'Imagify_Notices' => $baseDir . '/inc/classes/class-imagify-notices.php',
+ 'Imagify_Notices_Deprecated' => $baseDir . '/inc/deprecated/classes/class-imagify-notices-deprecated.php',
+ 'Imagify_Options' => $baseDir . '/inc/classes/class-imagify-options.php',
+ 'Imagify_Regenerate_Thumbnails_Deprecated' => $baseDir . '/inc/deprecated/classes/class-imagify-regenerate-thumbnails-deprecated.php',
+ 'Imagify_Requirements' => $baseDir . '/inc/classes/class-imagify-requirements.php',
+ 'Imagify_Settings' => $baseDir . '/inc/classes/class-imagify-settings.php',
+ 'Imagify_User' => $baseDir . '/inc/classes/class-imagify-user.php',
+ 'Imagify_Views' => $baseDir . '/inc/classes/class-imagify-views.php',
+ 'Imagify_WP_Async_Request' => $baseDir . '/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-async-request.php',
+ 'Imagify_WP_Background_Process' => $baseDir . '/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-background-process.php',
+);
diff --git a/wp-content/plugins/imagify/vendor/composer/autoload_namespaces.php b/wp-content/plugins/imagify/vendor/composer/autoload_namespaces.php
new file mode 100644
index 00000000..b7fc0125
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($baseDir . '/inc/3rd-party/wp-rocket/classes'),
+ 'Imagify\\ThirdParty\\RegenerateThumbnails\\' => array($baseDir . '/inc/3rd-party/regenerate-thumbnails/classes'),
+ 'Imagify\\ThirdParty\\NGG\\' => array($baseDir . '/inc/3rd-party/nextgen-gallery/classes'),
+ 'Imagify\\ThirdParty\\FormidablePro\\' => array($baseDir . '/inc/3rd-party/formidable-pro/classes'),
+ 'Imagify\\ThirdParty\\EnableMediaReplace\\' => array($baseDir . '/inc/3rd-party/enable-media-replace/classes'),
+ 'Imagify\\ThirdParty\\AS3CF\\' => array($baseDir . '/inc/3rd-party/amazon-s3-and-cloudfront/classes'),
+ 'Imagify\\Tests\\' => array($baseDir . '/Tests'),
+ 'Imagify\\Deprecated\\Traits\\' => array($baseDir . '/inc/deprecated/Traits'),
+ 'Imagify\\' => array($baseDir . '/classes'),
+ 'Dangoodman\\ComposerForWordpress\\' => array($vendorDir . '/dangoodman/composer-for-wordpress'),
+ 'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
+);
diff --git a/wp-content/plugins/imagify/vendor/composer/autoload_real.php b/wp-content/plugins/imagify/vendor/composer/autoload_real.php
new file mode 100644
index 00000000..b31de39c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/autoload_real.php
@@ -0,0 +1,46 @@
+= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInitee5f379dcd1f5ddc8ae5536c97dc2605::getInitializer($loader));
+ } else {
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->setClassMapAuthoritative(true);
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/autoload_static.php b/wp-content/plugins/imagify/vendor/composer/autoload_static.php
new file mode 100644
index 00000000..03ced78a
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/autoload_static.php
@@ -0,0 +1,306 @@
+
+ array (
+ 'Imagify\\ThirdParty\\WPRocket\\' => 28,
+ 'Imagify\\ThirdParty\\RegenerateThumbnails\\' => 40,
+ 'Imagify\\ThirdParty\\NGG\\' => 23,
+ 'Imagify\\ThirdParty\\FormidablePro\\' => 33,
+ 'Imagify\\ThirdParty\\EnableMediaReplace\\' => 38,
+ 'Imagify\\ThirdParty\\AS3CF\\' => 25,
+ 'Imagify\\Tests\\' => 14,
+ 'Imagify\\Deprecated\\Traits\\' => 26,
+ 'Imagify\\' => 8,
+ ),
+ 'D' =>
+ array (
+ 'Dangoodman\\ComposerForWordpress\\' => 32,
+ ),
+ 'C' =>
+ array (
+ 'Composer\\Installers\\' => 20,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Imagify\\ThirdParty\\WPRocket\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/inc/3rd-party/wp-rocket/classes',
+ ),
+ 'Imagify\\ThirdParty\\RegenerateThumbnails\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/inc/3rd-party/regenerate-thumbnails/classes',
+ ),
+ 'Imagify\\ThirdParty\\NGG\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes',
+ ),
+ 'Imagify\\ThirdParty\\FormidablePro\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/inc/3rd-party/formidable-pro/classes',
+ ),
+ 'Imagify\\ThirdParty\\EnableMediaReplace\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/inc/3rd-party/enable-media-replace/classes',
+ ),
+ 'Imagify\\ThirdParty\\AS3CF\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/inc/3rd-party/amazon-s3-and-cloudfront/classes',
+ ),
+ 'Imagify\\Tests\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/Tests',
+ ),
+ 'Imagify\\Deprecated\\Traits\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/inc/deprecated/Traits',
+ ),
+ 'Imagify\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/classes',
+ ),
+ 'Dangoodman\\ComposerForWordpress\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/dangoodman/composer-for-wordpress',
+ ),
+ 'Composer\\Installers\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\Installers\\AglInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AglInstaller.php',
+ 'Composer\\Installers\\AimeosInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AimeosInstaller.php',
+ 'Composer\\Installers\\AnnotateCmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php',
+ 'Composer\\Installers\\AsgardInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AsgardInstaller.php',
+ 'Composer\\Installers\\AttogramInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/AttogramInstaller.php',
+ 'Composer\\Installers\\BaseInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/BaseInstaller.php',
+ 'Composer\\Installers\\BitrixInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/BitrixInstaller.php',
+ 'Composer\\Installers\\BonefishInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/BonefishInstaller.php',
+ 'Composer\\Installers\\CakePHPInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CakePHPInstaller.php',
+ 'Composer\\Installers\\ChefInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ChefInstaller.php',
+ 'Composer\\Installers\\CiviCrmInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CiviCrmInstaller.php',
+ 'Composer\\Installers\\ClanCatsFrameworkInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php',
+ 'Composer\\Installers\\CockpitInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CockpitInstaller.php',
+ 'Composer\\Installers\\CodeIgniterInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php',
+ 'Composer\\Installers\\Concrete5Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Concrete5Installer.php',
+ 'Composer\\Installers\\CraftInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CraftInstaller.php',
+ 'Composer\\Installers\\CroogoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/CroogoInstaller.php',
+ 'Composer\\Installers\\DecibelInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DecibelInstaller.php',
+ 'Composer\\Installers\\DframeInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DframeInstaller.php',
+ 'Composer\\Installers\\DokuWikiInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DokuWikiInstaller.php',
+ 'Composer\\Installers\\DolibarrInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DolibarrInstaller.php',
+ 'Composer\\Installers\\DrupalInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/DrupalInstaller.php',
+ 'Composer\\Installers\\ElggInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ElggInstaller.php',
+ 'Composer\\Installers\\EliasisInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/EliasisInstaller.php',
+ 'Composer\\Installers\\ExpressionEngineInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php',
+ 'Composer\\Installers\\EzPlatformInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/EzPlatformInstaller.php',
+ 'Composer\\Installers\\FuelInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/FuelInstaller.php',
+ 'Composer\\Installers\\FuelphpInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/FuelphpInstaller.php',
+ 'Composer\\Installers\\GravInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/GravInstaller.php',
+ 'Composer\\Installers\\HuradInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/HuradInstaller.php',
+ 'Composer\\Installers\\ImageCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ImageCMSInstaller.php',
+ 'Composer\\Installers\\Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Installer.php',
+ 'Composer\\Installers\\ItopInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ItopInstaller.php',
+ 'Composer\\Installers\\JoomlaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/JoomlaInstaller.php',
+ 'Composer\\Installers\\KanboardInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KanboardInstaller.php',
+ 'Composer\\Installers\\KirbyInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KirbyInstaller.php',
+ 'Composer\\Installers\\KnownInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KnownInstaller.php',
+ 'Composer\\Installers\\KodiCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KodiCMSInstaller.php',
+ 'Composer\\Installers\\KohanaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/KohanaInstaller.php',
+ 'Composer\\Installers\\LanManagementSystemInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php',
+ 'Composer\\Installers\\LaravelInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/LaravelInstaller.php',
+ 'Composer\\Installers\\LavaLiteInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/LavaLiteInstaller.php',
+ 'Composer\\Installers\\LithiumInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/LithiumInstaller.php',
+ 'Composer\\Installers\\MODULEWorkInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php',
+ 'Composer\\Installers\\MODXEvoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MODXEvoInstaller.php',
+ 'Composer\\Installers\\MagentoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MagentoInstaller.php',
+ 'Composer\\Installers\\MajimaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MajimaInstaller.php',
+ 'Composer\\Installers\\MakoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MakoInstaller.php',
+ 'Composer\\Installers\\MantisBTInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MantisBTInstaller.php',
+ 'Composer\\Installers\\MauticInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MauticInstaller.php',
+ 'Composer\\Installers\\MayaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MayaInstaller.php',
+ 'Composer\\Installers\\MediaWikiInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MediaWikiInstaller.php',
+ 'Composer\\Installers\\MicroweberInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MicroweberInstaller.php',
+ 'Composer\\Installers\\ModxInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ModxInstaller.php',
+ 'Composer\\Installers\\MoodleInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/MoodleInstaller.php',
+ 'Composer\\Installers\\OctoberInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/OctoberInstaller.php',
+ 'Composer\\Installers\\OntoWikiInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/OntoWikiInstaller.php',
+ 'Composer\\Installers\\OsclassInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/OsclassInstaller.php',
+ 'Composer\\Installers\\OxidInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/OxidInstaller.php',
+ 'Composer\\Installers\\PPIInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PPIInstaller.php',
+ 'Composer\\Installers\\PhiftyInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PhiftyInstaller.php',
+ 'Composer\\Installers\\PhpBBInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PhpBBInstaller.php',
+ 'Composer\\Installers\\PimcoreInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PimcoreInstaller.php',
+ 'Composer\\Installers\\PiwikInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PiwikInstaller.php',
+ 'Composer\\Installers\\PlentymarketsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php',
+ 'Composer\\Installers\\Plugin' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Plugin.php',
+ 'Composer\\Installers\\PortoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PortoInstaller.php',
+ 'Composer\\Installers\\PrestashopInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PrestashopInstaller.php',
+ 'Composer\\Installers\\ProcessWireInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ProcessWireInstaller.php',
+ 'Composer\\Installers\\PuppetInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PuppetInstaller.php',
+ 'Composer\\Installers\\PxcmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/PxcmsInstaller.php',
+ 'Composer\\Installers\\RadPHPInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/RadPHPInstaller.php',
+ 'Composer\\Installers\\ReIndexInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ReIndexInstaller.php',
+ 'Composer\\Installers\\Redaxo5Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Redaxo5Installer.php',
+ 'Composer\\Installers\\RedaxoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/RedaxoInstaller.php',
+ 'Composer\\Installers\\RoundcubeInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/RoundcubeInstaller.php',
+ 'Composer\\Installers\\SMFInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SMFInstaller.php',
+ 'Composer\\Installers\\ShopwareInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ShopwareInstaller.php',
+ 'Composer\\Installers\\SilverStripeInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SilverStripeInstaller.php',
+ 'Composer\\Installers\\SiteDirectInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SiteDirectInstaller.php',
+ 'Composer\\Installers\\StarbugInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/StarbugInstaller.php',
+ 'Composer\\Installers\\SyDESInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SyDESInstaller.php',
+ 'Composer\\Installers\\SyliusInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/SyliusInstaller.php',
+ 'Composer\\Installers\\Symfony1Installer' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/Symfony1Installer.php',
+ 'Composer\\Installers\\TYPO3CmsInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php',
+ 'Composer\\Installers\\TYPO3FlowInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php',
+ 'Composer\\Installers\\TaoInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TaoInstaller.php',
+ 'Composer\\Installers\\TheliaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TheliaInstaller.php',
+ 'Composer\\Installers\\TuskInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/TuskInstaller.php',
+ 'Composer\\Installers\\UserFrostingInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/UserFrostingInstaller.php',
+ 'Composer\\Installers\\VanillaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/VanillaInstaller.php',
+ 'Composer\\Installers\\VgmcpInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/VgmcpInstaller.php',
+ 'Composer\\Installers\\WHMCSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WHMCSInstaller.php',
+ 'Composer\\Installers\\WolfCMSInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WolfCMSInstaller.php',
+ 'Composer\\Installers\\WordPressInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/WordPressInstaller.php',
+ 'Composer\\Installers\\YawikInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/YawikInstaller.php',
+ 'Composer\\Installers\\ZendInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ZendInstaller.php',
+ 'Composer\\Installers\\ZikulaInstaller' => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers/ZikulaInstaller.php',
+ 'Dangoodman\\ComposerForWordpress\\ComposerForWordpress' => __DIR__ . '/..' . '/dangoodman/composer-for-wordpress/ComposerForWordpress.php',
+ 'Imagify' => __DIR__ . '/../..' . '/inc/classes/class-imagify.php',
+ 'Imagify\\Auth\\Basic' => __DIR__ . '/../..' . '/classes/Auth/Basic.php',
+ 'Imagify\\Bulk\\AbstractBulk' => __DIR__ . '/../..' . '/classes/Bulk/AbstractBulk.php',
+ 'Imagify\\Bulk\\BulkInterface' => __DIR__ . '/../..' . '/classes/Bulk/BulkInterface.php',
+ 'Imagify\\Bulk\\CustomFolders' => __DIR__ . '/../..' . '/classes/Bulk/CustomFolders.php',
+ 'Imagify\\Bulk\\Noop' => __DIR__ . '/../..' . '/classes/Bulk/Noop.php',
+ 'Imagify\\Bulk\\WP' => __DIR__ . '/../..' . '/classes/Bulk/WP.php',
+ 'Imagify\\CDN\\PushCDNInterface' => __DIR__ . '/../..' . '/classes/CDN/PushCDNInterface.php',
+ 'Imagify\\Context\\AbstractContext' => __DIR__ . '/../..' . '/classes/Context/AbstractContext.php',
+ 'Imagify\\Context\\ContextInterface' => __DIR__ . '/../..' . '/classes/Context/ContextInterface.php',
+ 'Imagify\\Context\\CustomFolders' => __DIR__ . '/../..' . '/classes/Context/CustomFolders.php',
+ 'Imagify\\Context\\Noop' => __DIR__ . '/../..' . '/classes/Context/Noop.php',
+ 'Imagify\\Context\\WP' => __DIR__ . '/../..' . '/classes/Context/WP.php',
+ 'Imagify\\DB\\DBInterface' => __DIR__ . '/../..' . '/classes/DB/DBInterface.php',
+ 'Imagify\\Deprecated\\Traits\\Media\\CustomFoldersDeprecatedTrait' => __DIR__ . '/../..' . '/inc/deprecated/Traits/Media/CustomFoldersDeprecatedTrait.php',
+ 'Imagify\\Deprecated\\Traits\\Media\\NGGDeprecatedTrait' => __DIR__ . '/../..' . '/inc/deprecated/Traits/Media/NGGDeprecatedTrait.php',
+ 'Imagify\\Deprecated\\Traits\\Media\\NoopDeprecatedTrait' => __DIR__ . '/../..' . '/inc/deprecated/Traits/Media/NoopDeprecatedTrait.php',
+ 'Imagify\\Deprecated\\Traits\\Media\\WPDeprecatedTrait' => __DIR__ . '/../..' . '/inc/deprecated/Traits/Media/WPDeprecatedTrait.php',
+ 'Imagify\\Deprecated\\Traits\\Optimization\\Process\\AbstractProcessDeprecatedTrait' => __DIR__ . '/../..' . '/inc/deprecated/Traits/Optimization/Process/AbstractProcessDeprecatedTrait.php',
+ 'Imagify\\Imagifybeat\\Actions' => __DIR__ . '/../..' . '/classes/Imagifybeat/Actions.php',
+ 'Imagify\\Imagifybeat\\Core' => __DIR__ . '/../..' . '/classes/Imagifybeat/Core.php',
+ 'Imagify\\Job\\MediaOptimization' => __DIR__ . '/../..' . '/classes/Job/MediaOptimization.php',
+ 'Imagify\\Media\\AbstractMedia' => __DIR__ . '/../..' . '/classes/Media/AbstractMedia.php',
+ 'Imagify\\Media\\CustomFolders' => __DIR__ . '/../..' . '/classes/Media/CustomFolders.php',
+ 'Imagify\\Media\\MediaInterface' => __DIR__ . '/../..' . '/classes/Media/MediaInterface.php',
+ 'Imagify\\Media\\Noop' => __DIR__ . '/../..' . '/classes/Media/Noop.php',
+ 'Imagify\\Media\\WP' => __DIR__ . '/../..' . '/classes/Media/WP.php',
+ 'Imagify\\Optimization\\Data\\AbstractData' => __DIR__ . '/../..' . '/classes/Optimization/Data/AbstractData.php',
+ 'Imagify\\Optimization\\Data\\CustomFolders' => __DIR__ . '/../..' . '/classes/Optimization/Data/CustomFolders.php',
+ 'Imagify\\Optimization\\Data\\DataInterface' => __DIR__ . '/../..' . '/classes/Optimization/Data/DataInterface.php',
+ 'Imagify\\Optimization\\Data\\Noop' => __DIR__ . '/../..' . '/classes/Optimization/Data/Noop.php',
+ 'Imagify\\Optimization\\Data\\WP' => __DIR__ . '/../..' . '/classes/Optimization/Data/WP.php',
+ 'Imagify\\Optimization\\File' => __DIR__ . '/../..' . '/classes/Optimization/File.php',
+ 'Imagify\\Optimization\\Process\\AbstractProcess' => __DIR__ . '/../..' . '/classes/Optimization/Process/AbstractProcess.php',
+ 'Imagify\\Optimization\\Process\\CustomFolders' => __DIR__ . '/../..' . '/classes/Optimization/Process/CustomFolders.php',
+ 'Imagify\\Optimization\\Process\\Noop' => __DIR__ . '/../..' . '/classes/Optimization/Process/Noop.php',
+ 'Imagify\\Optimization\\Process\\ProcessInterface' => __DIR__ . '/../..' . '/classes/Optimization/Process/ProcessInterface.php',
+ 'Imagify\\Optimization\\Process\\WP' => __DIR__ . '/../..' . '/classes/Optimization/Process/WP.php',
+ 'Imagify\\Stats\\OptimizedMediaWithoutWebp' => __DIR__ . '/../..' . '/classes/Stats/OptimizedMediaWithoutWebp.php',
+ 'Imagify\\Stats\\StatInterface' => __DIR__ . '/../..' . '/classes/Stats/StatInterface.php',
+ 'Imagify\\ThirdParty\\AS3CF\\CDN\\WP\\AS3' => __DIR__ . '/../..' . '/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php',
+ 'Imagify\\ThirdParty\\AS3CF\\Main' => __DIR__ . '/../..' . '/inc/3rd-party/amazon-s3-and-cloudfront/classes/Main.php',
+ 'Imagify\\ThirdParty\\EnableMediaReplace\\Main' => __DIR__ . '/../..' . '/inc/3rd-party/enable-media-replace/classes/Main.php',
+ 'Imagify\\ThirdParty\\FormidablePro\\Main' => __DIR__ . '/../..' . '/inc/3rd-party/formidable-pro/classes/Main.php',
+ 'Imagify\\ThirdParty\\NGG\\Bulk\\NGG' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/Bulk/NGG.php',
+ 'Imagify\\ThirdParty\\NGG\\Context\\NGG' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/Context/NGG.php',
+ 'Imagify\\ThirdParty\\NGG\\DB' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/DB.php',
+ 'Imagify\\ThirdParty\\NGG\\DynamicThumbnails' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/DynamicThumbnails.php',
+ 'Imagify\\ThirdParty\\NGG\\Main' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/Main.php',
+ 'Imagify\\ThirdParty\\NGG\\Media\\NGG' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php',
+ 'Imagify\\ThirdParty\\NGG\\NGGStorage' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/NGGStorage.php',
+ 'Imagify\\ThirdParty\\NGG\\Optimization\\Data\\NGG' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php',
+ 'Imagify\\ThirdParty\\NGG\\Optimization\\Process\\NGG' => __DIR__ . '/../..' . '/inc/3rd-party/nextgen-gallery/classes/Optimization/Process/NGG.php',
+ 'Imagify\\ThirdParty\\RegenerateThumbnails\\Main' => __DIR__ . '/../..' . '/inc/3rd-party/regenerate-thumbnails/classes/Main.php',
+ 'Imagify\\ThirdParty\\WPRocket\\Main' => __DIR__ . '/../..' . '/inc/3rd-party/wp-rocket/classes/Main.php',
+ 'Imagify\\Traits\\InstanceGetterTrait' => __DIR__ . '/../..' . '/classes/Traits/InstanceGetterTrait.php',
+ 'Imagify\\Traits\\MediaRowTrait' => __DIR__ . '/../..' . '/classes/Traits/MediaRowTrait.php',
+ 'Imagify\\Webp\\Apache' => __DIR__ . '/../..' . '/classes/Webp/Apache.php',
+ 'Imagify\\Webp\\Display' => __DIR__ . '/../..' . '/classes/Webp/Display.php',
+ 'Imagify\\Webp\\IIS' => __DIR__ . '/../..' . '/classes/Webp/IIS.php',
+ 'Imagify\\Webp\\Picture\\Display' => __DIR__ . '/../..' . '/classes/Webp/Picture/Display.php',
+ 'Imagify\\Webp\\RewriteRules\\Apache' => __DIR__ . '/../..' . '/classes/Webp/RewriteRules/Apache.php',
+ 'Imagify\\Webp\\RewriteRules\\Display' => __DIR__ . '/../..' . '/classes/Webp/RewriteRules/Display.php',
+ 'Imagify\\Webp\\RewriteRules\\IIS' => __DIR__ . '/../..' . '/classes/Webp/RewriteRules/IIS.php',
+ 'Imagify\\Webp\\RewriteRules\\Nginx' => __DIR__ . '/../..' . '/classes/Webp/RewriteRules/Nginx.php',
+ 'Imagify\\WriteFile\\AbstractApacheDirConfFile' => __DIR__ . '/../..' . '/classes/WriteFile/AbstractApacheDirConfFile.php',
+ 'Imagify\\WriteFile\\AbstractIISDirConfFile' => __DIR__ . '/../..' . '/classes/WriteFile/AbstractIISDirConfFile.php',
+ 'Imagify\\WriteFile\\AbstractNginxDirConfFile' => __DIR__ . '/../..' . '/classes/WriteFile/AbstractNginxDirConfFile.php',
+ 'Imagify\\WriteFile\\AbstractWriteDirConfFile' => __DIR__ . '/../..' . '/classes/WriteFile/AbstractWriteDirConfFile.php',
+ 'Imagify\\WriteFile\\WriteFileInterface' => __DIR__ . '/../..' . '/classes/WriteFile/WriteFileInterface.php',
+ 'Imagify_AS3CF_Attachment' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-as3cf-attachment.php',
+ 'Imagify_AS3CF_Deprecated' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-as3cf-deprecated.php',
+ 'Imagify_Abstract_Attachment' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-abstract-attachment.php',
+ 'Imagify_Abstract_Background_Process' => __DIR__ . '/../..' . '/inc/classes/class-imagify-abstract-background-process.php',
+ 'Imagify_Abstract_Cron' => __DIR__ . '/../..' . '/inc/classes/class-imagify-abstract-cron.php',
+ 'Imagify_Abstract_DB' => __DIR__ . '/../..' . '/inc/classes/class-imagify-abstract-db.php',
+ 'Imagify_Abstract_DB_Deprecated' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-abstract-db-deprecated.php',
+ 'Imagify_Abstract_Options' => __DIR__ . '/../..' . '/inc/classes/class-imagify-abstract-options.php',
+ 'Imagify_Admin_Ajax_Post' => __DIR__ . '/../..' . '/inc/classes/class-imagify-admin-ajax-post.php',
+ 'Imagify_Admin_Ajax_Post_Deprecated' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-admin-ajax-post-deprecated.php',
+ 'Imagify_Assets' => __DIR__ . '/../..' . '/inc/classes/class-imagify-assets.php',
+ 'Imagify_Assets_Deprecated' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-assets-deprecated.php',
+ 'Imagify_Attachment' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-attachment.php',
+ 'Imagify_Auto_Optimization' => __DIR__ . '/../..' . '/inc/classes/class-imagify-auto-optimization.php',
+ 'Imagify_Auto_Optimization_Deprecated' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-auto-optimization-deprecated.php',
+ 'Imagify_Cron_Library_Size' => __DIR__ . '/../..' . '/inc/classes/class-imagify-cron-library-size.php',
+ 'Imagify_Cron_Rating' => __DIR__ . '/../..' . '/inc/classes/class-imagify-cron-rating.php',
+ 'Imagify_Cron_Sync_Files' => __DIR__ . '/../..' . '/inc/classes/class-imagify-cron-sync-files.php',
+ 'Imagify_Custom_Folders' => __DIR__ . '/../..' . '/inc/classes/class-imagify-custom-folders.php',
+ 'Imagify_DB' => __DIR__ . '/../..' . '/inc/classes/class-imagify-db.php',
+ 'Imagify_Data' => __DIR__ . '/../..' . '/inc/classes/class-imagify-data.php',
+ 'Imagify_Enable_Media_Replace_Deprecated' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-enable-media-replace-deprecated.php',
+ 'Imagify_File_Attachment' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-file-attachment.php',
+ 'Imagify_Files_DB' => __DIR__ . '/../..' . '/inc/classes/class-imagify-files-db.php',
+ 'Imagify_Files_Iterator' => __DIR__ . '/../..' . '/inc/classes/class-imagify-files-iterator.php',
+ 'Imagify_Files_List_Table' => __DIR__ . '/../..' . '/inc/classes/class-imagify-files-list-table.php',
+ 'Imagify_Files_Recursive_Iterator' => __DIR__ . '/../..' . '/inc/classes/class-imagify-files-recursive-iterator.php',
+ 'Imagify_Files_Scan' => __DIR__ . '/../..' . '/inc/classes/class-imagify-files-scan.php',
+ 'Imagify_Files_Stats' => __DIR__ . '/../..' . '/inc/classes/class-imagify-files-stats.php',
+ 'Imagify_Filesystem' => __DIR__ . '/../..' . '/inc/classes/class-imagify-filesystem.php',
+ 'Imagify_Folders_DB' => __DIR__ . '/../..' . '/inc/classes/class-imagify-folders-db.php',
+ 'Imagify_NGG_Attachment' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-ngg-attachment.php',
+ 'Imagify_NGG_Dynamic_Thumbnails_Background_Process' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-ngg-dynamic-thumbnails-background-process.php',
+ 'Imagify_Notices' => __DIR__ . '/../..' . '/inc/classes/class-imagify-notices.php',
+ 'Imagify_Notices_Deprecated' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-notices-deprecated.php',
+ 'Imagify_Options' => __DIR__ . '/../..' . '/inc/classes/class-imagify-options.php',
+ 'Imagify_Regenerate_Thumbnails_Deprecated' => __DIR__ . '/../..' . '/inc/deprecated/classes/class-imagify-regenerate-thumbnails-deprecated.php',
+ 'Imagify_Requirements' => __DIR__ . '/../..' . '/inc/classes/class-imagify-requirements.php',
+ 'Imagify_Settings' => __DIR__ . '/../..' . '/inc/classes/class-imagify-settings.php',
+ 'Imagify_User' => __DIR__ . '/../..' . '/inc/classes/class-imagify-user.php',
+ 'Imagify_Views' => __DIR__ . '/../..' . '/inc/classes/class-imagify-views.php',
+ 'Imagify_WP_Async_Request' => __DIR__ . '/../..' . '/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-async-request.php',
+ 'Imagify_WP_Background_Process' => __DIR__ . '/../..' . '/inc/classes/Dependencies/deliciousbrains/wp-background-processing/classes/wp-background-process.php',
+ );
+
+ public static function getInitializer(ClassLoaderWPMediaImagifyWordPressPlugin $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitee5f379dcd1f5ddc8ae5536c97dc2605::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitee5f379dcd1f5ddc8ae5536c97dc2605::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitee5f379dcd1f5ddc8ae5536c97dc2605::$classMap;
+
+ }, null, ClassLoaderWPMediaImagifyWordPressPlugin::class);
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installed.json b/wp-content/plugins/imagify/vendor/composer/installed.json
new file mode 100644
index 00000000..74d92f91
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installed.json
@@ -0,0 +1,182 @@
+[
+ {
+ "name": "composer/installers",
+ "version": "v1.10.0",
+ "version_normalized": "1.10.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/installers.git",
+ "reference": "1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/installers/zipball/1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d",
+ "reference": "1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0"
+ },
+ "replace": {
+ "roundcube/plugin-installer": "*",
+ "shama/baton": "*"
+ },
+ "require-dev": {
+ "composer/composer": "1.6.* || ^2.0",
+ "composer/semver": "^1 || ^3",
+ "phpstan/phpstan": "^0.12.55",
+ "phpstan/phpstan-phpunit": "^0.12.16",
+ "symfony/phpunit-bridge": "^4.2 || ^5",
+ "symfony/process": "^2.3"
+ },
+ "time": "2021-01-14T11:07:16+00:00",
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Composer\\Installers\\Plugin",
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Composer\\Installers\\": "src/Composer/Installers"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kyle Robinson Young",
+ "email": "kyle@dontkry.com",
+ "homepage": "https://github.com/shama"
+ }
+ ],
+ "description": "A multi-framework Composer library installer",
+ "homepage": "https://composer.github.io/installers/",
+ "keywords": [
+ "Craft",
+ "Dolibarr",
+ "Eliasis",
+ "Hurad",
+ "ImageCMS",
+ "Kanboard",
+ "Lan Management System",
+ "MODX Evo",
+ "MantisBT",
+ "Mautic",
+ "Maya",
+ "OXID",
+ "Plentymarkets",
+ "Porto",
+ "RadPHP",
+ "SMF",
+ "Starbug",
+ "Thelia",
+ "Whmcs",
+ "WolfCMS",
+ "agl",
+ "aimeos",
+ "annotatecms",
+ "attogram",
+ "bitrix",
+ "cakephp",
+ "chef",
+ "cockpit",
+ "codeigniter",
+ "concrete5",
+ "croogo",
+ "dokuwiki",
+ "drupal",
+ "eZ Platform",
+ "elgg",
+ "expressionengine",
+ "fuelphp",
+ "grav",
+ "installer",
+ "itop",
+ "joomla",
+ "known",
+ "kohana",
+ "laravel",
+ "lavalite",
+ "lithium",
+ "magento",
+ "majima",
+ "mako",
+ "mediawiki",
+ "modulework",
+ "modx",
+ "moodle",
+ "osclass",
+ "phpbb",
+ "piwik",
+ "ppi",
+ "processwire",
+ "puppet",
+ "pxcms",
+ "reindex",
+ "roundcube",
+ "shopware",
+ "silverstripe",
+ "sydes",
+ "sylius",
+ "symfony",
+ "typo3",
+ "wordpress",
+ "yawik",
+ "zend",
+ "zikula"
+ ],
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ]
+ },
+ {
+ "name": "dangoodman/composer-for-wordpress",
+ "version": "2.0.2",
+ "version_normalized": "2.0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dangoodman/composer-for-wordpress.git",
+ "reference": "cc5b3d0a1122d87d60f378071159bac0dbd93daa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dangoodman/composer-for-wordpress/zipball/cc5b3d0a1122d87d60f378071159bac0dbd93daa",
+ "reference": "cc5b3d0a1122d87d60f378071159bac0dbd93daa",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0"
+ },
+ "time": "2020-11-16T19:32:10+00:00",
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Dangoodman\\ComposerForWordpress\\ComposerForWordpress"
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Dangoodman\\ComposerForWordpress\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ]
+ }
+]
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/continuous-integration.yml b/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/continuous-integration.yml
new file mode 100644
index 00000000..840e4482
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/continuous-integration.yml
@@ -0,0 +1,70 @@
+name: "Continuous Integration"
+
+on:
+ - push
+ - pull_request
+
+env:
+ COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist"
+ SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT: "1"
+
+jobs:
+ tests:
+ name: "CI"
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php-version:
+ - "5.3"
+ - "5.4"
+ - "5.5"
+ - "5.6"
+ - "7.0"
+ - "7.1"
+ - "7.2"
+ - "7.3"
+ - "7.4"
+ - "8.0"
+ - "8.1"
+ dependencies: [locked]
+ include:
+ - php-version: "5.3"
+ dependencies: lowest
+ - php-version: "8.1"
+ dependencies: lowest
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ coverage: "none"
+ php-version: "${{ matrix.php-version }}"
+
+ - name: Get composer cache directory
+ id: composercache
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composercache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: "Handle lowest dependencies update"
+ if: "contains(matrix.dependencies, 'lowest')"
+ run: "echo \"COMPOSER_FLAGS=$COMPOSER_FLAGS --prefer-lowest\" >> $GITHUB_ENV"
+
+ - name: "Install latest dependencies"
+ run: |
+ # Remove PHPStan as it requires a newer PHP
+ composer remove phpstan/phpstan phpstan/phpstan-phpunit --dev --no-update
+ composer update ${{ env.COMPOSER_FLAGS }}
+
+ - name: "Run tests"
+ run: "vendor/bin/simple-phpunit --verbose"
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/lint.yml b/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/lint.yml
new file mode 100644
index 00000000..81a1ac4d
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/lint.yml
@@ -0,0 +1,30 @@
+name: "PHP Lint"
+
+on:
+ - push
+ - pull_request
+
+jobs:
+ tests:
+ name: "Lint"
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php-version:
+ - "5.3"
+ - "8.0"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ coverage: "none"
+ php-version: "${{ matrix.php-version }}"
+
+ - name: "Lint PHP files"
+ run: "find src/ -type f -name '*.php' -print0 | xargs -0 -L1 -P4 -- php -l -f"
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/phpstan.yml b/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/phpstan.yml
new file mode 100644
index 00000000..ac4c4a92
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/.github/workflows/phpstan.yml
@@ -0,0 +1,51 @@
+name: "PHPStan"
+
+on:
+ - push
+ - pull_request
+
+env:
+ COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist"
+ SYMFONY_PHPUNIT_VERSION: ""
+
+jobs:
+ tests:
+ name: "PHPStan"
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php-version:
+ # pinned to 7.4 because we need PHPUnit 7.5 which does not support PHP 8
+ - "7.4"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2"
+
+ - name: "Install PHP"
+ uses: "shivammathur/setup-php@v2"
+ with:
+ coverage: "none"
+ php-version: "${{ matrix.php-version }}"
+
+ - name: Get composer cache directory
+ id: composercache
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+
+ - name: Cache dependencies
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composercache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: "Install latest dependencies"
+ run: "composer update ${{ env.COMPOSER_FLAGS }}"
+
+ - name: Run PHPStan
+ # Locked to phpunit 7.5 here as newer ones have void return types which break inheritance
+ run: |
+ composer require --dev phpunit/phpunit:^7.5.20 --with-all-dependencies ${{ env.COMPOSER_FLAGS }}
+ vendor/bin/phpstan analyse
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/LICENSE b/wp-content/plugins/imagify/vendor/composer/installers/LICENSE
new file mode 100644
index 00000000..85f97fc7
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Kyle Robinson Young
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/composer.json b/wp-content/plugins/imagify/vendor/composer/installers/composer.json
new file mode 100644
index 00000000..61c0b7f4
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/composer.json
@@ -0,0 +1,119 @@
+{
+ "name": "composer/installers",
+ "type": "composer-plugin",
+ "license": "MIT",
+ "description": "A multi-framework Composer library installer",
+ "keywords": [
+ "installer",
+ "Aimeos",
+ "AGL",
+ "AnnotateCms",
+ "Attogram",
+ "Bitrix",
+ "CakePHP",
+ "Chef",
+ "Cockpit",
+ "CodeIgniter",
+ "concrete5",
+ "Craft",
+ "Croogo",
+ "DokuWiki",
+ "Dolibarr",
+ "Drupal",
+ "Elgg",
+ "Eliasis",
+ "ExpressionEngine",
+ "eZ Platform",
+ "FuelPHP",
+ "Grav",
+ "Hurad",
+ "ImageCMS",
+ "iTop",
+ "Joomla",
+ "Kanboard",
+ "Known",
+ "Kohana",
+ "Lan Management System",
+ "Laravel",
+ "Lavalite",
+ "Lithium",
+ "Magento",
+ "majima",
+ "Mako",
+ "MantisBT",
+ "Mautic",
+ "Maya",
+ "MODX",
+ "MODX Evo",
+ "MediaWiki",
+ "OXID",
+ "osclass",
+ "MODULEWork",
+ "Moodle",
+ "Piwik",
+ "pxcms",
+ "phpBB",
+ "Plentymarkets",
+ "PPI",
+ "Puppet",
+ "Porto",
+ "ProcessWire",
+ "RadPHP",
+ "ReIndex",
+ "Roundcube",
+ "shopware",
+ "SilverStripe",
+ "SMF",
+ "Starbug",
+ "SyDES",
+ "Sylius",
+ "symfony",
+ "Thelia",
+ "TYPO3",
+ "WHMCS",
+ "WolfCMS",
+ "WordPress",
+ "YAWIK",
+ "Zend",
+ "Zikula"
+ ],
+ "homepage": "https://composer.github.io/installers/",
+ "authors": [
+ {
+ "name": "Kyle Robinson Young",
+ "email": "kyle@dontkry.com",
+ "homepage": "https://github.com/shama"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "Composer\\Installers\\": "src/Composer/Installers" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Composer\\Installers\\Test\\": "tests/Composer/Installers/Test" }
+ },
+ "extra": {
+ "class": "Composer\\Installers\\Plugin",
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ }
+ },
+ "replace": {
+ "shama/baton": "*",
+ "roundcube/plugin-installer": "*"
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "1.6.* || ^2.0",
+ "composer/semver": "^1 || ^3",
+ "symfony/phpunit-bridge": "^4.2 || ^5",
+ "phpstan/phpstan": "^0.12.55",
+ "symfony/process": "^2.3",
+ "phpstan/phpstan-phpunit": "^0.12.16"
+ },
+ "scripts": {
+ "test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit",
+ "phpstan": "vendor/bin/phpstan analyse"
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/phpstan.neon.dist b/wp-content/plugins/imagify/vendor/composer/installers/phpstan.neon.dist
new file mode 100644
index 00000000..8e3d81e9
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/phpstan.neon.dist
@@ -0,0 +1,10 @@
+parameters:
+ level: 5
+ paths:
+ - src
+ - tests
+ excludes_analyse:
+ - tests/Composer/Installers/Test/PolyfillTestCase.php
+
+includes:
+ - vendor/phpstan/phpstan-phpunit/extension.neon
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AglInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AglInstaller.php
new file mode 100644
index 00000000..01b8a416
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AglInstaller.php
@@ -0,0 +1,21 @@
+ 'More/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = preg_replace_callback('/(?:^|_|-)(.?)/', function ($matches) {
+ return strtoupper($matches[1]);
+ }, $vars['name']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AimeosInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AimeosInstaller.php
new file mode 100644
index 00000000..79a0e958
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AimeosInstaller.php
@@ -0,0 +1,9 @@
+ 'ext/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php
new file mode 100644
index 00000000..89d7ad90
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php
@@ -0,0 +1,11 @@
+ 'addons/modules/{$name}/',
+ 'component' => 'addons/components/{$name}/',
+ 'service' => 'addons/services/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AsgardInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AsgardInstaller.php
new file mode 100644
index 00000000..22dad1b9
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AsgardInstaller.php
@@ -0,0 +1,49 @@
+ 'Modules/{$name}/',
+ 'theme' => 'Themes/{$name}/'
+ );
+
+ /**
+ * Format package name.
+ *
+ * For package type asgard-module, cut off a trailing '-plugin' if present.
+ *
+ * For package type asgard-theme, cut off a trailing '-theme' if present.
+ *
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] === 'asgard-module') {
+ return $this->inflectPluginVars($vars);
+ }
+
+ if ($vars['type'] === 'asgard-theme') {
+ return $this->inflectThemeVars($vars);
+ }
+
+ return $vars;
+ }
+
+ protected function inflectPluginVars($vars)
+ {
+ $vars['name'] = preg_replace('/-module$/', '', $vars['name']);
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+
+ protected function inflectThemeVars($vars)
+ {
+ $vars['name'] = preg_replace('/-theme$/', '', $vars['name']);
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AttogramInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AttogramInstaller.php
new file mode 100644
index 00000000..d62fd8fd
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/AttogramInstaller.php
@@ -0,0 +1,9 @@
+ 'modules/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BaseInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BaseInstaller.php
new file mode 100644
index 00000000..70dde907
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BaseInstaller.php
@@ -0,0 +1,137 @@
+composer = $composer;
+ $this->package = $package;
+ $this->io = $io;
+ }
+
+ /**
+ * Return the install path based on package type.
+ *
+ * @param PackageInterface $package
+ * @param string $frameworkType
+ * @return string
+ */
+ public function getInstallPath(PackageInterface $package, $frameworkType = '')
+ {
+ $type = $this->package->getType();
+
+ $prettyName = $this->package->getPrettyName();
+ if (strpos($prettyName, '/') !== false) {
+ list($vendor, $name) = explode('/', $prettyName);
+ } else {
+ $vendor = '';
+ $name = $prettyName;
+ }
+
+ $availableVars = $this->inflectPackageVars(compact('name', 'vendor', 'type'));
+
+ $extra = $package->getExtra();
+ if (!empty($extra['installer-name'])) {
+ $availableVars['name'] = $extra['installer-name'];
+ }
+
+ if ($this->composer->getPackage()) {
+ $extra = $this->composer->getPackage()->getExtra();
+ if (!empty($extra['installer-paths'])) {
+ $customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type, $vendor);
+ if ($customPath !== false) {
+ return $this->templatePath($customPath, $availableVars);
+ }
+ }
+ }
+
+ $packageType = substr($type, strlen($frameworkType) + 1);
+ $locations = $this->getLocations();
+ if (!isset($locations[$packageType])) {
+ throw new \InvalidArgumentException(sprintf('Package type "%s" is not supported', $type));
+ }
+
+ return $this->templatePath($locations[$packageType], $availableVars);
+ }
+
+ /**
+ * For an installer to override to modify the vars per installer.
+ *
+ * @param array $vars This will normally receive array{name: string, vendor: string, type: string}
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ return $vars;
+ }
+
+ /**
+ * Gets the installer's locations
+ *
+ * @return array map of package types => install path
+ */
+ public function getLocations()
+ {
+ return $this->locations;
+ }
+
+ /**
+ * Replace vars in a path
+ *
+ * @param string $path
+ * @param array $vars
+ * @return string
+ */
+ protected function templatePath($path, array $vars = array())
+ {
+ if (strpos($path, '{') !== false) {
+ extract($vars);
+ preg_match_all('@\{\$([A-Za-z0-9_]*)\}@i', $path, $matches);
+ if (!empty($matches[1])) {
+ foreach ($matches[1] as $var) {
+ $path = str_replace('{$' . $var . '}', $$var, $path);
+ }
+ }
+ }
+
+ return $path;
+ }
+
+ /**
+ * Search through a passed paths array for a custom install path.
+ *
+ * @param array $paths
+ * @param string $name
+ * @param string $type
+ * @param string $vendor = NULL
+ * @return string|false
+ */
+ protected function mapCustomInstallPaths(array $paths, $name, $type, $vendor = NULL)
+ {
+ foreach ($paths as $path => $names) {
+ $names = (array) $names;
+ if (in_array($name, $names) || in_array('type:' . $type, $names) || in_array('vendor:' . $vendor, $names)) {
+ return $path;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BitrixInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BitrixInstaller.php
new file mode 100644
index 00000000..e80cd1e1
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BitrixInstaller.php
@@ -0,0 +1,126 @@
+.`.
+ * - `bitrix-d7-component` â copy the component to directory `bitrix/components//`.
+ * - `bitrix-d7-template` â copy the template to directory `bitrix/templates/_`.
+ *
+ * You can set custom path to directory with Bitrix kernel in `composer.json`:
+ *
+ * ```json
+ * {
+ * "extra": {
+ * "bitrix-dir": "s1/bitrix"
+ * }
+ * }
+ * ```
+ *
+ * @author Nik Samokhvalov
+ * @author Denis Kulichkin
+ */
+class BitrixInstaller extends BaseInstaller
+{
+ protected $locations = array(
+ 'module' => '{$bitrix_dir}/modules/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
+ 'component' => '{$bitrix_dir}/components/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
+ 'theme' => '{$bitrix_dir}/templates/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken)
+ 'd7-module' => '{$bitrix_dir}/modules/{$vendor}.{$name}/',
+ 'd7-component' => '{$bitrix_dir}/components/{$vendor}/{$name}/',
+ 'd7-template' => '{$bitrix_dir}/templates/{$vendor}_{$name}/',
+ );
+
+ /**
+ * @var array Storage for informations about duplicates at all the time of installation packages.
+ */
+ private static $checkedDuplicates = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($this->composer->getPackage()) {
+ $extra = $this->composer->getPackage()->getExtra();
+
+ if (isset($extra['bitrix-dir'])) {
+ $vars['bitrix_dir'] = $extra['bitrix-dir'];
+ }
+ }
+
+ if (!isset($vars['bitrix_dir'])) {
+ $vars['bitrix_dir'] = 'bitrix';
+ }
+
+ return parent::inflectPackageVars($vars);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function templatePath($path, array $vars = array())
+ {
+ $templatePath = parent::templatePath($path, $vars);
+ $this->checkDuplicates($templatePath, $vars);
+
+ return $templatePath;
+ }
+
+ /**
+ * Duplicates search packages.
+ *
+ * @param string $path
+ * @param array $vars
+ */
+ protected function checkDuplicates($path, array $vars = array())
+ {
+ $packageType = substr($vars['type'], strlen('bitrix') + 1);
+ $localDir = explode('/', $vars['bitrix_dir']);
+ array_pop($localDir);
+ $localDir[] = 'local';
+ $localDir = implode('/', $localDir);
+
+ $oldPath = str_replace(
+ array('{$bitrix_dir}', '{$name}'),
+ array($localDir, $vars['name']),
+ $this->locations[$packageType]
+ );
+
+ if (in_array($oldPath, static::$checkedDuplicates)) {
+ return;
+ }
+
+ if ($oldPath !== $path && file_exists($oldPath) && $this->io && $this->io->isInteractive()) {
+
+ $this->io->writeError(' Duplication of packages: ');
+ $this->io->writeError(' Package ' . $oldPath . ' will be called instead package ' . $path . ' ');
+
+ while (true) {
+ switch ($this->io->ask(' Delete ' . $oldPath . ' [y,n,?]? ', '?')) {
+ case 'y':
+ $fs = new Filesystem();
+ $fs->removeDirectory($oldPath);
+ break 2;
+
+ case 'n':
+ break 2;
+
+ case '?':
+ default:
+ $this->io->writeError(array(
+ ' y - delete package ' . $oldPath . ' and to continue with the installation',
+ ' n - don\'t delete and to continue with the installation',
+ ));
+ $this->io->writeError(' ? - print help');
+ break;
+ }
+ }
+ }
+
+ static::$checkedDuplicates[] = $oldPath;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BonefishInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BonefishInstaller.php
new file mode 100644
index 00000000..da3aad2a
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/BonefishInstaller.php
@@ -0,0 +1,9 @@
+ 'Packages/{$vendor}/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php
new file mode 100644
index 00000000..dda90a4e
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php
@@ -0,0 +1,65 @@
+ 'Plugin/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($this->matchesCakeVersion('>=', '3.0.0')) {
+ return $vars;
+ }
+
+ $nameParts = explode('/', $vars['name']);
+ foreach ($nameParts as &$value) {
+ $value = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $value));
+ $value = str_replace(array('-', '_'), ' ', $value);
+ $value = str_replace(' ', '', ucwords($value));
+ }
+ $vars['name'] = implode('/', $nameParts);
+
+ return $vars;
+ }
+
+ /**
+ * Change the default plugin location when cakephp >= 3.0
+ */
+ public function getLocations()
+ {
+ if ($this->matchesCakeVersion('>=', '3.0.0')) {
+ $this->locations['plugin'] = $this->composer->getConfig()->get('vendor-dir') . '/{$vendor}/{$name}/';
+ }
+ return $this->locations;
+ }
+
+ /**
+ * Check if CakePHP version matches against a version
+ *
+ * @param string $matcher
+ * @param string $version
+ * @return bool
+ */
+ protected function matchesCakeVersion($matcher, $version)
+ {
+ $repositoryManager = $this->composer->getRepositoryManager();
+ if (! $repositoryManager) {
+ return false;
+ }
+
+ $repos = $repositoryManager->getLocalRepository();
+ if (!$repos) {
+ return false;
+ }
+
+ return $repos->findPackage('cakephp/cakephp', new Constraint($matcher, $version)) !== null;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ChefInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ChefInstaller.php
new file mode 100644
index 00000000..ab2f9aad
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ChefInstaller.php
@@ -0,0 +1,11 @@
+ 'Chef/{$vendor}/{$name}/',
+ 'role' => 'Chef/roles/{$name}/',
+ );
+}
+
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CiviCrmInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CiviCrmInstaller.php
new file mode 100644
index 00000000..6673aea9
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CiviCrmInstaller.php
@@ -0,0 +1,9 @@
+ 'ext/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php
new file mode 100644
index 00000000..c887815c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php
@@ -0,0 +1,10 @@
+ 'CCF/orbit/{$name}/',
+ 'theme' => 'CCF/app/themes/{$name}/',
+ );
+}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php
new file mode 100644
index 00000000..053f3ffd
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php
@@ -0,0 +1,32 @@
+ 'cockpit/modules/addons/{$name}/',
+ );
+
+ /**
+ * Format module name.
+ *
+ * Strip `module-` prefix from package name.
+ *
+ * {@inheritDoc}
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] == 'cockpit-module') {
+ return $this->inflectModuleVars($vars);
+ }
+
+ return $vars;
+ }
+
+ public function inflectModuleVars($vars)
+ {
+ $vars['name'] = ucfirst(preg_replace('/cockpit-/i', '', $vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php
new file mode 100644
index 00000000..3b4a4ece
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php
@@ -0,0 +1,11 @@
+ 'application/libraries/{$name}/',
+ 'third-party' => 'application/third_party/{$name}/',
+ 'module' => 'application/modules/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Concrete5Installer.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Concrete5Installer.php
new file mode 100644
index 00000000..5c01bafd
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Concrete5Installer.php
@@ -0,0 +1,13 @@
+ 'concrete/',
+ 'block' => 'application/blocks/{$name}/',
+ 'package' => 'packages/{$name}/',
+ 'theme' => 'application/themes/{$name}/',
+ 'update' => 'updates/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CraftInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CraftInstaller.php
new file mode 100644
index 00000000..d37a77ae
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CraftInstaller.php
@@ -0,0 +1,35 @@
+ 'craft/plugins/{$name}/',
+ );
+
+ /**
+ * Strip `craft-` prefix and/or `-plugin` suffix from package names
+ *
+ * @param array $vars
+ *
+ * @return array
+ */
+ final public function inflectPackageVars($vars)
+ {
+ return $this->inflectPluginVars($vars);
+ }
+
+ private function inflectPluginVars($vars)
+ {
+ $vars['name'] = preg_replace('/-' . self::NAME_SUFFIX . '$/i', '', $vars['name']);
+ $vars['name'] = preg_replace('/^' . self::NAME_PREFIX . '-/i', '', $vars['name']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CroogoInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CroogoInstaller.php
new file mode 100644
index 00000000..d94219d3
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/CroogoInstaller.php
@@ -0,0 +1,21 @@
+ 'Plugin/{$name}/',
+ 'theme' => 'View/Themed/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower(str_replace(array('-', '_'), ' ', $vars['name']));
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DecibelInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DecibelInstaller.php
new file mode 100644
index 00000000..f4837a6c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DecibelInstaller.php
@@ -0,0 +1,10 @@
+ 'app/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DframeInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DframeInstaller.php
new file mode 100644
index 00000000..70788163
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DframeInstaller.php
@@ -0,0 +1,10 @@
+ 'modules/{$vendor}/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DokuWikiInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DokuWikiInstaller.php
new file mode 100644
index 00000000..cfd638d5
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DokuWikiInstaller.php
@@ -0,0 +1,50 @@
+ 'lib/plugins/{$name}/',
+ 'template' => 'lib/tpl/{$name}/',
+ );
+
+ /**
+ * Format package name.
+ *
+ * For package type dokuwiki-plugin, cut off a trailing '-plugin',
+ * or leading dokuwiki_ if present.
+ *
+ * For package type dokuwiki-template, cut off a trailing '-template' if present.
+ *
+ */
+ public function inflectPackageVars($vars)
+ {
+
+ if ($vars['type'] === 'dokuwiki-plugin') {
+ return $this->inflectPluginVars($vars);
+ }
+
+ if ($vars['type'] === 'dokuwiki-template') {
+ return $this->inflectTemplateVars($vars);
+ }
+
+ return $vars;
+ }
+
+ protected function inflectPluginVars($vars)
+ {
+ $vars['name'] = preg_replace('/-plugin$/', '', $vars['name']);
+ $vars['name'] = preg_replace('/^dokuwiki_?-?/', '', $vars['name']);
+
+ return $vars;
+ }
+
+ protected function inflectTemplateVars($vars)
+ {
+ $vars['name'] = preg_replace('/-template$/', '', $vars['name']);
+ $vars['name'] = preg_replace('/^dokuwiki_?-?/', '', $vars['name']);
+
+ return $vars;
+ }
+
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DolibarrInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DolibarrInstaller.php
new file mode 100644
index 00000000..21f7e8e8
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DolibarrInstaller.php
@@ -0,0 +1,16 @@
+
+ */
+class DolibarrInstaller extends BaseInstaller
+{
+ //TODO: Add support for scripts and themes
+ protected $locations = array(
+ 'module' => 'htdocs/custom/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DrupalInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DrupalInstaller.php
new file mode 100644
index 00000000..73282392
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/DrupalInstaller.php
@@ -0,0 +1,22 @@
+ 'core/',
+ 'module' => 'modules/{$name}/',
+ 'theme' => 'themes/{$name}/',
+ 'library' => 'libraries/{$name}/',
+ 'profile' => 'profiles/{$name}/',
+ 'database-driver' => 'drivers/lib/Drupal/Driver/Database/{$name}/',
+ 'drush' => 'drush/{$name}/',
+ 'custom-theme' => 'themes/custom/{$name}/',
+ 'custom-module' => 'modules/custom/{$name}/',
+ 'custom-profile' => 'profiles/custom/{$name}/',
+ 'drupal-multisite' => 'sites/{$name}/',
+ 'console' => 'console/{$name}/',
+ 'console-language' => 'console/language/{$name}/',
+ 'config' => 'config/sync/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ElggInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ElggInstaller.php
new file mode 100644
index 00000000..c0bb609f
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ElggInstaller.php
@@ -0,0 +1,9 @@
+ 'mod/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/EliasisInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/EliasisInstaller.php
new file mode 100644
index 00000000..6f3dc97b
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/EliasisInstaller.php
@@ -0,0 +1,12 @@
+ 'components/{$name}/',
+ 'module' => 'modules/{$name}/',
+ 'plugin' => 'plugins/{$name}/',
+ 'template' => 'templates/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php
new file mode 100644
index 00000000..d5321a8c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php
@@ -0,0 +1,29 @@
+ 'system/expressionengine/third_party/{$name}/',
+ 'theme' => 'themes/third_party/{$name}/',
+ );
+
+ private $ee3Locations = array(
+ 'addon' => 'system/user/addons/{$name}/',
+ 'theme' => 'themes/user/{$name}/',
+ );
+
+ public function getInstallPath(PackageInterface $package, $frameworkType = '')
+ {
+
+ $version = "{$frameworkType}Locations";
+ $this->locations = $this->$version;
+
+ return parent::getInstallPath($package, $frameworkType);
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/EzPlatformInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/EzPlatformInstaller.php
new file mode 100644
index 00000000..f30ebcc7
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/EzPlatformInstaller.php
@@ -0,0 +1,10 @@
+ 'web/assets/ezplatform/',
+ 'assets' => 'web/assets/ezplatform/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/FuelInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/FuelInstaller.php
new file mode 100644
index 00000000..6eba2e34
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/FuelInstaller.php
@@ -0,0 +1,11 @@
+ 'fuel/app/modules/{$name}/',
+ 'package' => 'fuel/packages/{$name}/',
+ 'theme' => 'fuel/app/themes/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/FuelphpInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/FuelphpInstaller.php
new file mode 100644
index 00000000..29d980b3
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/FuelphpInstaller.php
@@ -0,0 +1,9 @@
+ 'components/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/GravInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/GravInstaller.php
new file mode 100644
index 00000000..dbe63e07
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/GravInstaller.php
@@ -0,0 +1,30 @@
+ 'user/plugins/{$name}/',
+ 'theme' => 'user/themes/{$name}/',
+ );
+
+ /**
+ * Format package name
+ *
+ * @param array $vars
+ *
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ $restrictedWords = implode('|', array_keys($this->locations));
+
+ $vars['name'] = strtolower($vars['name']);
+ $vars['name'] = preg_replace('/^(?:grav-)?(?:(?:'.$restrictedWords.')-)?(.*?)(?:-(?:'.$restrictedWords.'))?$/ui',
+ '$1',
+ $vars['name']
+ );
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/HuradInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/HuradInstaller.php
new file mode 100644
index 00000000..8fe017f0
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/HuradInstaller.php
@@ -0,0 +1,25 @@
+ 'plugins/{$name}/',
+ 'theme' => 'plugins/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $nameParts = explode('/', $vars['name']);
+ foreach ($nameParts as &$value) {
+ $value = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $value));
+ $value = str_replace(array('-', '_'), ' ', $value);
+ $value = str_replace(' ', '', ucwords($value));
+ }
+ $vars['name'] = implode('/', $nameParts);
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ImageCMSInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ImageCMSInstaller.php
new file mode 100644
index 00000000..5e2142ea
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ImageCMSInstaller.php
@@ -0,0 +1,11 @@
+ 'templates/{$name}/',
+ 'module' => 'application/modules/{$name}/',
+ 'library' => 'application/libraries/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Installer.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Installer.php
new file mode 100644
index 00000000..5869c177
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Installer.php
@@ -0,0 +1,294 @@
+ 'AimeosInstaller',
+ 'asgard' => 'AsgardInstaller',
+ 'attogram' => 'AttogramInstaller',
+ 'agl' => 'AglInstaller',
+ 'annotatecms' => 'AnnotateCmsInstaller',
+ 'bitrix' => 'BitrixInstaller',
+ 'bonefish' => 'BonefishInstaller',
+ 'cakephp' => 'CakePHPInstaller',
+ 'chef' => 'ChefInstaller',
+ 'civicrm' => 'CiviCrmInstaller',
+ 'ccframework' => 'ClanCatsFrameworkInstaller',
+ 'cockpit' => 'CockpitInstaller',
+ 'codeigniter' => 'CodeIgniterInstaller',
+ 'concrete5' => 'Concrete5Installer',
+ 'craft' => 'CraftInstaller',
+ 'croogo' => 'CroogoInstaller',
+ 'dframe' => 'DframeInstaller',
+ 'dokuwiki' => 'DokuWikiInstaller',
+ 'dolibarr' => 'DolibarrInstaller',
+ 'decibel' => 'DecibelInstaller',
+ 'drupal' => 'DrupalInstaller',
+ 'elgg' => 'ElggInstaller',
+ 'eliasis' => 'EliasisInstaller',
+ 'ee3' => 'ExpressionEngineInstaller',
+ 'ee2' => 'ExpressionEngineInstaller',
+ 'ezplatform' => 'EzPlatformInstaller',
+ 'fuel' => 'FuelInstaller',
+ 'fuelphp' => 'FuelphpInstaller',
+ 'grav' => 'GravInstaller',
+ 'hurad' => 'HuradInstaller',
+ 'imagecms' => 'ImageCMSInstaller',
+ 'itop' => 'ItopInstaller',
+ 'joomla' => 'JoomlaInstaller',
+ 'kanboard' => 'KanboardInstaller',
+ 'kirby' => 'KirbyInstaller',
+ 'known' => 'KnownInstaller',
+ 'kodicms' => 'KodiCMSInstaller',
+ 'kohana' => 'KohanaInstaller',
+ 'lms' => 'LanManagementSystemInstaller',
+ 'laravel' => 'LaravelInstaller',
+ 'lavalite' => 'LavaLiteInstaller',
+ 'lithium' => 'LithiumInstaller',
+ 'magento' => 'MagentoInstaller',
+ 'majima' => 'MajimaInstaller',
+ 'mantisbt' => 'MantisBTInstaller',
+ 'mako' => 'MakoInstaller',
+ 'maya' => 'MayaInstaller',
+ 'mautic' => 'MauticInstaller',
+ 'mediawiki' => 'MediaWikiInstaller',
+ 'microweber' => 'MicroweberInstaller',
+ 'modulework' => 'MODULEWorkInstaller',
+ 'modx' => 'ModxInstaller',
+ 'modxevo' => 'MODXEvoInstaller',
+ 'moodle' => 'MoodleInstaller',
+ 'october' => 'OctoberInstaller',
+ 'ontowiki' => 'OntoWikiInstaller',
+ 'oxid' => 'OxidInstaller',
+ 'osclass' => 'OsclassInstaller',
+ 'pxcms' => 'PxcmsInstaller',
+ 'phpbb' => 'PhpBBInstaller',
+ 'pimcore' => 'PimcoreInstaller',
+ 'piwik' => 'PiwikInstaller',
+ 'plentymarkets'=> 'PlentymarketsInstaller',
+ 'ppi' => 'PPIInstaller',
+ 'puppet' => 'PuppetInstaller',
+ 'radphp' => 'RadPHPInstaller',
+ 'phifty' => 'PhiftyInstaller',
+ 'porto' => 'PortoInstaller',
+ 'processwire' => 'ProcessWireInstaller',
+ 'redaxo' => 'RedaxoInstaller',
+ 'redaxo5' => 'Redaxo5Installer',
+ 'reindex' => 'ReIndexInstaller',
+ 'roundcube' => 'RoundcubeInstaller',
+ 'shopware' => 'ShopwareInstaller',
+ 'sitedirect' => 'SiteDirectInstaller',
+ 'silverstripe' => 'SilverStripeInstaller',
+ 'smf' => 'SMFInstaller',
+ 'starbug' => 'StarbugInstaller',
+ 'sydes' => 'SyDESInstaller',
+ 'sylius' => 'SyliusInstaller',
+ 'symfony1' => 'Symfony1Installer',
+ 'tao' => 'TaoInstaller',
+ 'thelia' => 'TheliaInstaller',
+ 'tusk' => 'TuskInstaller',
+ 'typo3-cms' => 'TYPO3CmsInstaller',
+ 'typo3-flow' => 'TYPO3FlowInstaller',
+ 'userfrosting' => 'UserFrostingInstaller',
+ 'vanilla' => 'VanillaInstaller',
+ 'whmcs' => 'WHMCSInstaller',
+ 'wolfcms' => 'WolfCMSInstaller',
+ 'wordpress' => 'WordPressInstaller',
+ 'yawik' => 'YawikInstaller',
+ 'zend' => 'ZendInstaller',
+ 'zikula' => 'ZikulaInstaller',
+ 'prestashop' => 'PrestashopInstaller'
+ );
+
+ /**
+ * Installer constructor.
+ *
+ * Disables installers specified in main composer extra installer-disable
+ * list
+ *
+ * @param IOInterface $io
+ * @param Composer $composer
+ * @param string $type
+ * @param Filesystem|null $filesystem
+ * @param BinaryInstaller|null $binaryInstaller
+ */
+ public function __construct(
+ IOInterface $io,
+ Composer $composer,
+ $type = 'library',
+ Filesystem $filesystem = null,
+ BinaryInstaller $binaryInstaller = null
+ ) {
+ parent::__construct($io, $composer, $type, $filesystem,
+ $binaryInstaller);
+ $this->removeDisabledInstallers();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getInstallPath(PackageInterface $package)
+ {
+ $type = $package->getType();
+ $frameworkType = $this->findFrameworkType($type);
+
+ if ($frameworkType === false) {
+ throw new \InvalidArgumentException(
+ 'Sorry the package type of this package is not yet supported.'
+ );
+ }
+
+ $class = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType];
+ $installer = new $class($package, $this->composer, $this->getIO());
+
+ return $installer->getInstallPath($package, $frameworkType);
+ }
+
+ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
+ {
+ $installPath = $this->getPackageBasePath($package);
+ $io = $this->io;
+ $outputStatus = function () use ($io, $installPath) {
+ $io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? 'deleted ' : 'not deleted '));
+ };
+
+ $promise = parent::uninstall($repo, $package);
+
+ // Composer v2 might return a promise here
+ if ($promise instanceof PromiseInterface) {
+ return $promise->then($outputStatus);
+ }
+
+ // If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async)
+ $outputStatus();
+
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function supports($packageType)
+ {
+ $frameworkType = $this->findFrameworkType($packageType);
+
+ if ($frameworkType === false) {
+ return false;
+ }
+
+ $locationPattern = $this->getLocationPattern($frameworkType);
+
+ return preg_match('#' . $frameworkType . '-' . $locationPattern . '#', $packageType, $matches) === 1;
+ }
+
+ /**
+ * Finds a supported framework type if it exists and returns it
+ *
+ * @param string $type
+ * @return string|false
+ */
+ protected function findFrameworkType($type)
+ {
+ krsort($this->supportedTypes);
+
+ foreach ($this->supportedTypes as $key => $val) {
+ if ($key === substr($type, 0, strlen($key))) {
+ return substr($type, 0, strlen($key));
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the second part of the regular expression to check for support of a
+ * package type
+ *
+ * @param string $frameworkType
+ * @return string
+ */
+ protected function getLocationPattern($frameworkType)
+ {
+ $pattern = false;
+ if (!empty($this->supportedTypes[$frameworkType])) {
+ $frameworkClass = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType];
+ /** @var BaseInstaller $framework */
+ $framework = new $frameworkClass(null, $this->composer, $this->getIO());
+ $locations = array_keys($framework->getLocations());
+ $pattern = $locations ? '(' . implode('|', $locations) . ')' : false;
+ }
+
+ return $pattern ? : '(\w+)';
+ }
+
+ /**
+ * Get I/O object
+ *
+ * @return IOInterface
+ */
+ private function getIO()
+ {
+ return $this->io;
+ }
+
+ /**
+ * Look for installers set to be disabled in composer's extra config and
+ * remove them from the list of supported installers.
+ *
+ * Globals:
+ * - true, "all", and "*" - disable all installers.
+ * - false - enable all installers (useful with
+ * wikimedia/composer-merge-plugin or similar)
+ *
+ * @return void
+ */
+ protected function removeDisabledInstallers()
+ {
+ $extra = $this->composer->getPackage()->getExtra();
+
+ if (!isset($extra['installer-disable']) || $extra['installer-disable'] === false) {
+ // No installers are disabled
+ return;
+ }
+
+ // Get installers to disable
+ $disable = $extra['installer-disable'];
+
+ // Ensure $disabled is an array
+ if (!is_array($disable)) {
+ $disable = array($disable);
+ }
+
+ // Check which installers should be disabled
+ $all = array(true, "all", "*");
+ $intersect = array_intersect($all, $disable);
+ if (!empty($intersect)) {
+ // Disable all installers
+ $this->supportedTypes = array();
+ } else {
+ // Disable specified installers
+ foreach ($disable as $key => $installer) {
+ if (is_string($installer) && key_exists($installer, $this->supportedTypes)) {
+ unset($this->supportedTypes[$installer]);
+ }
+ }
+ }
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ItopInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ItopInstaller.php
new file mode 100644
index 00000000..c6c1b337
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ItopInstaller.php
@@ -0,0 +1,9 @@
+ 'extensions/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/JoomlaInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/JoomlaInstaller.php
new file mode 100644
index 00000000..9ee77596
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/JoomlaInstaller.php
@@ -0,0 +1,15 @@
+ 'components/{$name}/',
+ 'module' => 'modules/{$name}/',
+ 'template' => 'templates/{$name}/',
+ 'plugin' => 'plugins/{$name}/',
+ 'library' => 'libraries/{$name}/',
+ );
+
+ // TODO: Add inflector for mod_ and com_ names
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KanboardInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KanboardInstaller.php
new file mode 100644
index 00000000..9cb7b8cd
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KanboardInstaller.php
@@ -0,0 +1,18 @@
+ 'plugins/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KirbyInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KirbyInstaller.php
new file mode 100644
index 00000000..36b2f84a
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KirbyInstaller.php
@@ -0,0 +1,11 @@
+ 'site/plugins/{$name}/',
+ 'field' => 'site/fields/{$name}/',
+ 'tag' => 'site/tags/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KnownInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KnownInstaller.php
new file mode 100644
index 00000000..c5d08c5f
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KnownInstaller.php
@@ -0,0 +1,11 @@
+ 'IdnoPlugins/{$name}/',
+ 'theme' => 'Themes/{$name}/',
+ 'console' => 'ConsolePlugins/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KodiCMSInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KodiCMSInstaller.php
new file mode 100644
index 00000000..7143e232
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KodiCMSInstaller.php
@@ -0,0 +1,10 @@
+ 'cms/plugins/{$name}/',
+ 'media' => 'cms/media/vendor/{$name}/'
+ );
+}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KohanaInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KohanaInstaller.php
new file mode 100644
index 00000000..dcd6d263
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/KohanaInstaller.php
@@ -0,0 +1,9 @@
+ 'modules/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php
new file mode 100644
index 00000000..903143a5
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php
@@ -0,0 +1,27 @@
+ 'plugins/{$name}/',
+ 'template' => 'templates/{$name}/',
+ 'document-template' => 'documents/templates/{$name}/',
+ 'userpanel-module' => 'userpanel/modules/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LaravelInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LaravelInstaller.php
new file mode 100644
index 00000000..be4d53a7
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LaravelInstaller.php
@@ -0,0 +1,9 @@
+ 'libraries/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LavaLiteInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LavaLiteInstaller.php
new file mode 100644
index 00000000..412c0b5c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LavaLiteInstaller.php
@@ -0,0 +1,10 @@
+ 'packages/{$vendor}/{$name}/',
+ 'theme' => 'public/themes/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LithiumInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LithiumInstaller.php
new file mode 100644
index 00000000..47bbd4ca
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/LithiumInstaller.php
@@ -0,0 +1,10 @@
+ 'libraries/{$name}/',
+ 'source' => 'libraries/_source/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php
new file mode 100644
index 00000000..9c2e9fb4
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php
@@ -0,0 +1,9 @@
+ 'modules/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MODXEvoInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MODXEvoInstaller.php
new file mode 100644
index 00000000..5a664608
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MODXEvoInstaller.php
@@ -0,0 +1,16 @@
+ 'assets/snippets/{$name}/',
+ 'plugin' => 'assets/plugins/{$name}/',
+ 'module' => 'assets/modules/{$name}/',
+ 'template' => 'assets/templates/{$name}/',
+ 'lib' => 'assets/lib/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MagentoInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MagentoInstaller.php
new file mode 100644
index 00000000..cf18e947
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MagentoInstaller.php
@@ -0,0 +1,11 @@
+ 'app/design/frontend/{$name}/',
+ 'skin' => 'skin/frontend/default/{$name}/',
+ 'library' => 'lib/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MajimaInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MajimaInstaller.php
new file mode 100644
index 00000000..e463756f
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MajimaInstaller.php
@@ -0,0 +1,37 @@
+ 'plugins/{$name}/',
+ );
+
+ /**
+ * Transforms the names
+ * @param array $vars
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ return $this->correctPluginName($vars);
+ }
+
+ /**
+ * Change hyphenated names to camelcase
+ * @param array $vars
+ * @return array
+ */
+ private function correctPluginName($vars)
+ {
+ $camelCasedName = preg_replace_callback('/(-[a-z])/', function ($matches) {
+ return strtoupper($matches[0][1]);
+ }, $vars['name']);
+ $vars['name'] = ucfirst($camelCasedName);
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MakoInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MakoInstaller.php
new file mode 100644
index 00000000..ca3cfacb
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MakoInstaller.php
@@ -0,0 +1,9 @@
+ 'app/packages/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MantisBTInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MantisBTInstaller.php
new file mode 100644
index 00000000..dadb1dbb
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MantisBTInstaller.php
@@ -0,0 +1,23 @@
+ 'plugins/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MauticInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MauticInstaller.php
new file mode 100644
index 00000000..3e1ce2b2
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MauticInstaller.php
@@ -0,0 +1,25 @@
+ 'plugins/{$name}/',
+ 'theme' => 'themes/{$name}/',
+ );
+
+ /**
+ * Format package name of mautic-plugins to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] == 'mautic-plugin') {
+ $vars['name'] = preg_replace_callback('/(-[a-z])/', function ($matches) {
+ return strtoupper($matches[0][1]);
+ }, ucfirst($vars['name']));
+ }
+
+ return $vars;
+ }
+
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MayaInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MayaInstaller.php
new file mode 100644
index 00000000..30a91676
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MayaInstaller.php
@@ -0,0 +1,33 @@
+ 'modules/{$name}/',
+ );
+
+ /**
+ * Format package name.
+ *
+ * For package type maya-module, cut off a trailing '-module' if present.
+ *
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] === 'maya-module') {
+ return $this->inflectModuleVars($vars);
+ }
+
+ return $vars;
+ }
+
+ protected function inflectModuleVars($vars)
+ {
+ $vars['name'] = preg_replace('/-module$/', '', $vars['name']);
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MediaWikiInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MediaWikiInstaller.php
new file mode 100644
index 00000000..f5a8957e
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MediaWikiInstaller.php
@@ -0,0 +1,51 @@
+ 'core/',
+ 'extension' => 'extensions/{$name}/',
+ 'skin' => 'skins/{$name}/',
+ );
+
+ /**
+ * Format package name.
+ *
+ * For package type mediawiki-extension, cut off a trailing '-extension' if present and transform
+ * to CamelCase keeping existing uppercase chars.
+ *
+ * For package type mediawiki-skin, cut off a trailing '-skin' if present.
+ *
+ */
+ public function inflectPackageVars($vars)
+ {
+
+ if ($vars['type'] === 'mediawiki-extension') {
+ return $this->inflectExtensionVars($vars);
+ }
+
+ if ($vars['type'] === 'mediawiki-skin') {
+ return $this->inflectSkinVars($vars);
+ }
+
+ return $vars;
+ }
+
+ protected function inflectExtensionVars($vars)
+ {
+ $vars['name'] = preg_replace('/-extension$/', '', $vars['name']);
+ $vars['name'] = str_replace('-', ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+
+ protected function inflectSkinVars($vars)
+ {
+ $vars['name'] = preg_replace('/-skin$/', '', $vars['name']);
+
+ return $vars;
+ }
+
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MicroweberInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MicroweberInstaller.php
new file mode 100644
index 00000000..b7d97039
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MicroweberInstaller.php
@@ -0,0 +1,119 @@
+ 'userfiles/modules/{$install_item_dir}/',
+ 'module-skin' => 'userfiles/modules/{$install_item_dir}/templates/',
+ 'template' => 'userfiles/templates/{$install_item_dir}/',
+ 'element' => 'userfiles/elements/{$install_item_dir}/',
+ 'vendor' => 'vendor/{$install_item_dir}/',
+ 'components' => 'components/{$install_item_dir}/'
+ );
+
+ /**
+ * Format package name.
+ *
+ * For package type microweber-module, cut off a trailing '-module' if present
+ *
+ * For package type microweber-template, cut off a trailing '-template' if present.
+ *
+ */
+ public function inflectPackageVars($vars)
+ {
+
+
+ if ($this->package->getTargetDir()) {
+ $vars['install_item_dir'] = $this->package->getTargetDir();
+ } else {
+ $vars['install_item_dir'] = $vars['name'];
+ if ($vars['type'] === 'microweber-template') {
+ return $this->inflectTemplateVars($vars);
+ }
+ if ($vars['type'] === 'microweber-templates') {
+ return $this->inflectTemplatesVars($vars);
+ }
+ if ($vars['type'] === 'microweber-core') {
+ return $this->inflectCoreVars($vars);
+ }
+ if ($vars['type'] === 'microweber-adapter') {
+ return $this->inflectCoreVars($vars);
+ }
+ if ($vars['type'] === 'microweber-module') {
+ return $this->inflectModuleVars($vars);
+ }
+ if ($vars['type'] === 'microweber-modules') {
+ return $this->inflectModulesVars($vars);
+ }
+ if ($vars['type'] === 'microweber-skin') {
+ return $this->inflectSkinVars($vars);
+ }
+ if ($vars['type'] === 'microweber-element' or $vars['type'] === 'microweber-elements') {
+ return $this->inflectElementVars($vars);
+ }
+ }
+
+
+ return $vars;
+ }
+
+ protected function inflectTemplateVars($vars)
+ {
+ $vars['install_item_dir'] = preg_replace('/-template$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/template-$/', '', $vars['install_item_dir']);
+
+ return $vars;
+ }
+
+ protected function inflectTemplatesVars($vars)
+ {
+ $vars['install_item_dir'] = preg_replace('/-templates$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/templates-$/', '', $vars['install_item_dir']);
+
+ return $vars;
+ }
+
+ protected function inflectCoreVars($vars)
+ {
+ $vars['install_item_dir'] = preg_replace('/-providers$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/-provider$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/-adapter$/', '', $vars['install_item_dir']);
+
+ return $vars;
+ }
+
+ protected function inflectModuleVars($vars)
+ {
+ $vars['install_item_dir'] = preg_replace('/-module$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/module-$/', '', $vars['install_item_dir']);
+
+ return $vars;
+ }
+
+ protected function inflectModulesVars($vars)
+ {
+ $vars['install_item_dir'] = preg_replace('/-modules$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/modules-$/', '', $vars['install_item_dir']);
+
+ return $vars;
+ }
+
+ protected function inflectSkinVars($vars)
+ {
+ $vars['install_item_dir'] = preg_replace('/-skin$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/skin-$/', '', $vars['install_item_dir']);
+
+ return $vars;
+ }
+
+ protected function inflectElementVars($vars)
+ {
+ $vars['install_item_dir'] = preg_replace('/-elements$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/elements-$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/-element$/', '', $vars['install_item_dir']);
+ $vars['install_item_dir'] = preg_replace('/element-$/', '', $vars['install_item_dir']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ModxInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ModxInstaller.php
new file mode 100644
index 00000000..0ee140ab
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ModxInstaller.php
@@ -0,0 +1,12 @@
+ 'core/packages/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php
new file mode 100644
index 00000000..05317995
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php
@@ -0,0 +1,59 @@
+ 'mod/{$name}/',
+ 'admin_report' => 'admin/report/{$name}/',
+ 'atto' => 'lib/editor/atto/plugins/{$name}/',
+ 'tool' => 'admin/tool/{$name}/',
+ 'assignment' => 'mod/assignment/type/{$name}/',
+ 'assignsubmission' => 'mod/assign/submission/{$name}/',
+ 'assignfeedback' => 'mod/assign/feedback/{$name}/',
+ 'auth' => 'auth/{$name}/',
+ 'availability' => 'availability/condition/{$name}/',
+ 'block' => 'blocks/{$name}/',
+ 'booktool' => 'mod/book/tool/{$name}/',
+ 'cachestore' => 'cache/stores/{$name}/',
+ 'cachelock' => 'cache/locks/{$name}/',
+ 'calendartype' => 'calendar/type/{$name}/',
+ 'fileconverter' => 'files/converter/{$name}/',
+ 'format' => 'course/format/{$name}/',
+ 'coursereport' => 'course/report/{$name}/',
+ 'customcertelement' => 'mod/customcert/element/{$name}/',
+ 'datafield' => 'mod/data/field/{$name}/',
+ 'datapreset' => 'mod/data/preset/{$name}/',
+ 'editor' => 'lib/editor/{$name}/',
+ 'enrol' => 'enrol/{$name}/',
+ 'filter' => 'filter/{$name}/',
+ 'gradeexport' => 'grade/export/{$name}/',
+ 'gradeimport' => 'grade/import/{$name}/',
+ 'gradereport' => 'grade/report/{$name}/',
+ 'gradingform' => 'grade/grading/form/{$name}/',
+ 'local' => 'local/{$name}/',
+ 'logstore' => 'admin/tool/log/store/{$name}/',
+ 'ltisource' => 'mod/lti/source/{$name}/',
+ 'ltiservice' => 'mod/lti/service/{$name}/',
+ 'message' => 'message/output/{$name}/',
+ 'mnetservice' => 'mnet/service/{$name}/',
+ 'plagiarism' => 'plagiarism/{$name}/',
+ 'portfolio' => 'portfolio/{$name}/',
+ 'qbehaviour' => 'question/behaviour/{$name}/',
+ 'qformat' => 'question/format/{$name}/',
+ 'qtype' => 'question/type/{$name}/',
+ 'quizaccess' => 'mod/quiz/accessrule/{$name}/',
+ 'quiz' => 'mod/quiz/report/{$name}/',
+ 'report' => 'report/{$name}/',
+ 'repository' => 'repository/{$name}/',
+ 'scormreport' => 'mod/scorm/report/{$name}/',
+ 'search' => 'search/engine/{$name}/',
+ 'theme' => 'theme/{$name}/',
+ 'tinymce' => 'lib/editor/tinymce/plugins/{$name}/',
+ 'profilefield' => 'user/profile/field/{$name}/',
+ 'webservice' => 'webservice/{$name}/',
+ 'workshopallocation' => 'mod/workshop/allocation/{$name}/',
+ 'workshopeval' => 'mod/workshop/eval/{$name}/',
+ 'workshopform' => 'mod/workshop/form/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php
new file mode 100644
index 00000000..08d5dc4e
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php
@@ -0,0 +1,47 @@
+ 'modules/{$name}/',
+ 'plugin' => 'plugins/{$vendor}/{$name}/',
+ 'theme' => 'themes/{$name}/'
+ );
+
+ /**
+ * Format package name.
+ *
+ * For package type october-plugin, cut off a trailing '-plugin' if present.
+ *
+ * For package type october-theme, cut off a trailing '-theme' if present.
+ *
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] === 'october-plugin') {
+ return $this->inflectPluginVars($vars);
+ }
+
+ if ($vars['type'] === 'october-theme') {
+ return $this->inflectThemeVars($vars);
+ }
+
+ return $vars;
+ }
+
+ protected function inflectPluginVars($vars)
+ {
+ $vars['name'] = preg_replace('/^oc-|-plugin$/', '', $vars['name']);
+ $vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']);
+
+ return $vars;
+ }
+
+ protected function inflectThemeVars($vars)
+ {
+ $vars['name'] = preg_replace('/^oc-|-theme$/', '', $vars['name']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OntoWikiInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OntoWikiInstaller.php
new file mode 100644
index 00000000..5dd3438d
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OntoWikiInstaller.php
@@ -0,0 +1,24 @@
+ 'extensions/{$name}/',
+ 'theme' => 'extensions/themes/{$name}/',
+ 'translation' => 'extensions/translations/{$name}/',
+ );
+
+ /**
+ * Format package name to lower case and remove ".ontowiki" suffix
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower($vars['name']);
+ $vars['name'] = preg_replace('/.ontowiki$/', '', $vars['name']);
+ $vars['name'] = preg_replace('/-theme$/', '', $vars['name']);
+ $vars['name'] = preg_replace('/-translation$/', '', $vars['name']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OsclassInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OsclassInstaller.php
new file mode 100644
index 00000000..3ca7954c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OsclassInstaller.php
@@ -0,0 +1,14 @@
+ 'oc-content/plugins/{$name}/',
+ 'theme' => 'oc-content/themes/{$name}/',
+ 'language' => 'oc-content/languages/{$name}/',
+ );
+
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OxidInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OxidInstaller.php
new file mode 100644
index 00000000..1797a22c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/OxidInstaller.php
@@ -0,0 +1,59 @@
+.+)\/.+/';
+
+ protected $locations = array(
+ 'module' => 'modules/{$name}/',
+ 'theme' => 'application/views/{$name}/',
+ 'out' => 'out/{$name}/',
+ );
+
+ /**
+ * getInstallPath
+ *
+ * @param PackageInterface $package
+ * @param string $frameworkType
+ * @return string
+ */
+ public function getInstallPath(PackageInterface $package, $frameworkType = '')
+ {
+ $installPath = parent::getInstallPath($package, $frameworkType);
+ $type = $this->package->getType();
+ if ($type === 'oxid-module') {
+ $this->prepareVendorDirectory($installPath);
+ }
+ return $installPath;
+ }
+
+ /**
+ * prepareVendorDirectory
+ *
+ * Makes sure there is a vendormetadata.php file inside
+ * the vendor folder if there is a vendor folder.
+ *
+ * @param string $installPath
+ * @return void
+ */
+ protected function prepareVendorDirectory($installPath)
+ {
+ $matches = '';
+ $hasVendorDirectory = preg_match(self::VENDOR_PATTERN, $installPath, $matches);
+ if (!$hasVendorDirectory) {
+ return;
+ }
+
+ $vendorDirectory = $matches['vendor'];
+ $vendorPath = getcwd() . '/modules/' . $vendorDirectory;
+ if (!file_exists($vendorPath)) {
+ mkdir($vendorPath, 0755, true);
+ }
+
+ $vendorMetaDataPath = $vendorPath . '/vendormetadata.php';
+ touch($vendorMetaDataPath);
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PPIInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PPIInstaller.php
new file mode 100644
index 00000000..170136f9
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PPIInstaller.php
@@ -0,0 +1,9 @@
+ 'modules/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PhiftyInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PhiftyInstaller.php
new file mode 100644
index 00000000..4e59a8a7
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PhiftyInstaller.php
@@ -0,0 +1,11 @@
+ 'bundles/{$name}/',
+ 'library' => 'libraries/{$name}/',
+ 'framework' => 'frameworks/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PhpBBInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PhpBBInstaller.php
new file mode 100644
index 00000000..deb2b77a
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PhpBBInstaller.php
@@ -0,0 +1,11 @@
+ 'ext/{$vendor}/{$name}/',
+ 'language' => 'language/{$name}/',
+ 'style' => 'styles/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PimcoreInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PimcoreInstaller.php
new file mode 100644
index 00000000..4781fa6d
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PimcoreInstaller.php
@@ -0,0 +1,21 @@
+ 'plugins/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PiwikInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PiwikInstaller.php
new file mode 100644
index 00000000..c17f4572
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PiwikInstaller.php
@@ -0,0 +1,32 @@
+ 'plugins/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ * @param array $vars
+ *
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php
new file mode 100644
index 00000000..903e55f6
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php
@@ -0,0 +1,29 @@
+ '{$name}/'
+ );
+
+ /**
+ * Remove hyphen, "plugin" and format to camelcase
+ * @param array $vars
+ *
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = explode("-", $vars['name']);
+ foreach ($vars['name'] as $key => $name) {
+ $vars['name'][$key] = ucfirst($vars['name'][$key]);
+ if (strcasecmp($name, "Plugin") == 0) {
+ unset($vars['name'][$key]);
+ }
+ }
+ $vars['name'] = implode("",$vars['name']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Plugin.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Plugin.php
new file mode 100644
index 00000000..e60da0e7
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Plugin.php
@@ -0,0 +1,27 @@
+installer = new Installer($io, $composer);
+ $composer->getInstallationManager()->addInstaller($this->installer);
+ }
+
+ public function deactivate(Composer $composer, IOInterface $io)
+ {
+ $composer->getInstallationManager()->removeInstaller($this->installer);
+ }
+
+ public function uninstall(Composer $composer, IOInterface $io)
+ {
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PortoInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PortoInstaller.php
new file mode 100644
index 00000000..dbf85e63
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PortoInstaller.php
@@ -0,0 +1,9 @@
+ 'app/Containers/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PrestashopInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PrestashopInstaller.php
new file mode 100644
index 00000000..4c8421e3
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PrestashopInstaller.php
@@ -0,0 +1,10 @@
+ 'modules/{$name}/',
+ 'theme' => 'themes/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php
new file mode 100644
index 00000000..e6834a0c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php
@@ -0,0 +1,22 @@
+ 'site/modules/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PuppetInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PuppetInstaller.php
new file mode 100644
index 00000000..77cc3dd8
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PuppetInstaller.php
@@ -0,0 +1,11 @@
+ 'modules/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PxcmsInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PxcmsInstaller.php
new file mode 100644
index 00000000..65510580
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/PxcmsInstaller.php
@@ -0,0 +1,63 @@
+ 'app/Modules/{$name}/',
+ 'theme' => 'themes/{$name}/',
+ );
+
+ /**
+ * Format package name.
+ *
+ * @param array $vars
+ *
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] === 'pxcms-module') {
+ return $this->inflectModuleVars($vars);
+ }
+
+ if ($vars['type'] === 'pxcms-theme') {
+ return $this->inflectThemeVars($vars);
+ }
+
+ return $vars;
+ }
+
+ /**
+ * For package type pxcms-module, cut off a trailing '-plugin' if present.
+ *
+ * return string
+ */
+ protected function inflectModuleVars($vars)
+ {
+ $vars['name'] = str_replace('pxcms-', '', $vars['name']); // strip out pxcms- just incase (legacy)
+ $vars['name'] = str_replace('module-', '', $vars['name']); // strip out module-
+ $vars['name'] = preg_replace('/-module$/', '', $vars['name']); // strip out -module
+ $vars['name'] = str_replace('-', '_', $vars['name']); // make -'s be _'s
+ $vars['name'] = ucwords($vars['name']); // make module name camelcased
+
+ return $vars;
+ }
+
+
+ /**
+ * For package type pxcms-module, cut off a trailing '-plugin' if present.
+ *
+ * return string
+ */
+ protected function inflectThemeVars($vars)
+ {
+ $vars['name'] = str_replace('pxcms-', '', $vars['name']); // strip out pxcms- just incase (legacy)
+ $vars['name'] = str_replace('theme-', '', $vars['name']); // strip out theme-
+ $vars['name'] = preg_replace('/-theme$/', '', $vars['name']); // strip out -theme
+ $vars['name'] = str_replace('-', '_', $vars['name']); // make -'s be _'s
+ $vars['name'] = ucwords($vars['name']); // make module name camelcased
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RadPHPInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RadPHPInstaller.php
new file mode 100644
index 00000000..0f78b5ca
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RadPHPInstaller.php
@@ -0,0 +1,24 @@
+ 'src/{$name}/'
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $nameParts = explode('/', $vars['name']);
+ foreach ($nameParts as &$value) {
+ $value = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $value));
+ $value = str_replace(array('-', '_'), ' ', $value);
+ $value = str_replace(' ', '', ucwords($value));
+ }
+ $vars['name'] = implode('/', $nameParts);
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ReIndexInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ReIndexInstaller.php
new file mode 100644
index 00000000..252c7339
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ReIndexInstaller.php
@@ -0,0 +1,10 @@
+ 'themes/{$name}/',
+ 'plugin' => 'plugins/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Redaxo5Installer.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Redaxo5Installer.php
new file mode 100644
index 00000000..23a20347
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Redaxo5Installer.php
@@ -0,0 +1,10 @@
+ 'redaxo/src/addons/{$name}/',
+ 'bestyle-plugin' => 'redaxo/src/addons/be_style/plugins/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RedaxoInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RedaxoInstaller.php
new file mode 100644
index 00000000..09544576
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RedaxoInstaller.php
@@ -0,0 +1,10 @@
+ 'redaxo/include/addons/{$name}/',
+ 'bestyle-plugin' => 'redaxo/include/addons/be_style/plugins/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RoundcubeInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RoundcubeInstaller.php
new file mode 100644
index 00000000..d8d795be
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/RoundcubeInstaller.php
@@ -0,0 +1,22 @@
+ 'plugins/{$name}/',
+ );
+
+ /**
+ * Lowercase name and changes the name to a underscores
+ *
+ * @param array $vars
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower(str_replace('-', '_', $vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SMFInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SMFInstaller.php
new file mode 100644
index 00000000..1acd3b14
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SMFInstaller.php
@@ -0,0 +1,10 @@
+ 'Sources/{$name}/',
+ 'theme' => 'Themes/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ShopwareInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ShopwareInstaller.php
new file mode 100644
index 00000000..7d20d27a
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ShopwareInstaller.php
@@ -0,0 +1,60 @@
+ 'engine/Shopware/Plugins/Local/Backend/{$name}/',
+ 'core-plugin' => 'engine/Shopware/Plugins/Local/Core/{$name}/',
+ 'frontend-plugin' => 'engine/Shopware/Plugins/Local/Frontend/{$name}/',
+ 'theme' => 'templates/{$name}/',
+ 'plugin' => 'custom/plugins/{$name}/',
+ 'frontend-theme' => 'themes/Frontend/{$name}/',
+ );
+
+ /**
+ * Transforms the names
+ * @param array $vars
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] === 'shopware-theme') {
+ return $this->correctThemeName($vars);
+ }
+
+ return $this->correctPluginName($vars);
+ }
+
+ /**
+ * Changes the name to a camelcased combination of vendor and name
+ * @param array $vars
+ * @return array
+ */
+ private function correctPluginName($vars)
+ {
+ $camelCasedName = preg_replace_callback('/(-[a-z])/', function ($matches) {
+ return strtoupper($matches[0][1]);
+ }, $vars['name']);
+
+ $vars['name'] = ucfirst($vars['vendor']) . ucfirst($camelCasedName);
+
+ return $vars;
+ }
+
+ /**
+ * Changes the name to a underscore separated name
+ * @param array $vars
+ * @return array
+ */
+ private function correctThemeName($vars)
+ {
+ $vars['name'] = str_replace('-', '_', $vars['name']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SilverStripeInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SilverStripeInstaller.php
new file mode 100644
index 00000000..81910e9f
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SilverStripeInstaller.php
@@ -0,0 +1,35 @@
+ '{$name}/',
+ 'theme' => 'themes/{$name}/',
+ );
+
+ /**
+ * Return the install path based on package type.
+ *
+ * Relies on built-in BaseInstaller behaviour with one exception: silverstripe/framework
+ * must be installed to 'sapphire' and not 'framework' if the version is <3.0.0
+ *
+ * @param PackageInterface $package
+ * @param string $frameworkType
+ * @return string
+ */
+ public function getInstallPath(PackageInterface $package, $frameworkType = '')
+ {
+ if (
+ $package->getName() == 'silverstripe/framework'
+ && preg_match('/^\d+\.\d+\.\d+/', $package->getVersion())
+ && version_compare($package->getVersion(), '2.999.999') < 0
+ ) {
+ return $this->templatePath($this->locations['module'], array('name' => 'sapphire'));
+ }
+
+ return parent::getInstallPath($package, $frameworkType);
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SiteDirectInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SiteDirectInstaller.php
new file mode 100644
index 00000000..762d94c6
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SiteDirectInstaller.php
@@ -0,0 +1,25 @@
+ 'modules/{$vendor}/{$name}/',
+ 'plugin' => 'plugins/{$vendor}/{$name}/'
+ );
+
+ public function inflectPackageVars($vars)
+ {
+ return $this->parseVars($vars);
+ }
+
+ protected function parseVars($vars)
+ {
+ $vars['vendor'] = strtolower($vars['vendor']) == 'sitedirect' ? 'SiteDirect' : $vars['vendor'];
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php
new file mode 100644
index 00000000..a31c9fda
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php
@@ -0,0 +1,12 @@
+ 'modules/{$name}/',
+ 'theme' => 'themes/{$name}/',
+ 'custom-module' => 'app/modules/{$name}/',
+ 'custom-theme' => 'app/themes/{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php
new file mode 100644
index 00000000..8626a9bc
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php
@@ -0,0 +1,47 @@
+ 'app/modules/{$name}/',
+ 'theme' => 'themes/{$name}/',
+ );
+
+ /**
+ * Format module name.
+ *
+ * Strip `sydes-` prefix and a trailing '-theme' or '-module' from package name if present.
+ *
+ * {@inerhitDoc}
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] == 'sydes-module') {
+ return $this->inflectModuleVars($vars);
+ }
+
+ if ($vars['type'] === 'sydes-theme') {
+ return $this->inflectThemeVars($vars);
+ }
+
+ return $vars;
+ }
+
+ public function inflectModuleVars($vars)
+ {
+ $vars['name'] = preg_replace('/(^sydes-|-module$)/i', '', $vars['name']);
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+
+ protected function inflectThemeVars($vars)
+ {
+ $vars['name'] = preg_replace('/(^sydes-|-theme$)/', '', $vars['name']);
+ $vars['name'] = strtolower($vars['name']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SyliusInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SyliusInstaller.php
new file mode 100644
index 00000000..4357a35b
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/SyliusInstaller.php
@@ -0,0 +1,9 @@
+ 'themes/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Symfony1Installer.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Symfony1Installer.php
new file mode 100644
index 00000000..1675c4f2
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/Symfony1Installer.php
@@ -0,0 +1,26 @@
+
+ */
+class Symfony1Installer extends BaseInstaller
+{
+ protected $locations = array(
+ 'plugin' => 'plugins/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = preg_replace_callback('/(-[a-z])/', function ($matches) {
+ return strtoupper($matches[0][1]);
+ }, $vars['name']);
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php
new file mode 100644
index 00000000..b1663e84
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php
@@ -0,0 +1,16 @@
+
+ */
+class TYPO3CmsInstaller extends BaseInstaller
+{
+ protected $locations = array(
+ 'extension' => 'typo3conf/ext/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php
new file mode 100644
index 00000000..42572f44
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php
@@ -0,0 +1,38 @@
+ 'Packages/Application/{$name}/',
+ 'framework' => 'Packages/Framework/{$name}/',
+ 'plugin' => 'Packages/Plugins/{$name}/',
+ 'site' => 'Packages/Sites/{$name}/',
+ 'boilerplate' => 'Packages/Boilerplates/{$name}/',
+ 'build' => 'Build/{$name}/',
+ );
+
+ /**
+ * Modify the package name to be a TYPO3 Flow style key.
+ *
+ * @param array $vars
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ $autoload = $this->package->getAutoload();
+ if (isset($autoload['psr-0']) && is_array($autoload['psr-0'])) {
+ $namespace = key($autoload['psr-0']);
+ $vars['name'] = str_replace('\\', '.', $namespace);
+ }
+ if (isset($autoload['psr-4']) && is_array($autoload['psr-4'])) {
+ $namespace = key($autoload['psr-4']);
+ $vars['name'] = rtrim(str_replace('\\', '.', $namespace), '.');
+ }
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TaoInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TaoInstaller.php
new file mode 100644
index 00000000..4f79a45f
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TaoInstaller.php
@@ -0,0 +1,30 @@
+ '{$name}'
+ );
+
+ public function inflectPackageVars($vars)
+ {
+ $extra = $this->package->getExtra();
+
+ if (array_key_exists(self::EXTRA_TAO_EXTENSION_NAME, $extra)) {
+ $vars['name'] = $extra[self::EXTRA_TAO_EXTENSION_NAME];
+ return $vars;
+ }
+
+ $vars['name'] = str_replace('extension-', '', $vars['name']);
+ $vars['name'] = str_replace('-', ' ', $vars['name']);
+ $vars['name'] = lcfirst(str_replace(' ', '', ucwords($vars['name'])));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TheliaInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TheliaInstaller.php
new file mode 100644
index 00000000..158af526
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TheliaInstaller.php
@@ -0,0 +1,12 @@
+ 'local/modules/{$name}/',
+ 'frontoffice-template' => 'templates/frontOffice/{$name}/',
+ 'backoffice-template' => 'templates/backOffice/{$name}/',
+ 'email-template' => 'templates/email/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TuskInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TuskInstaller.php
new file mode 100644
index 00000000..7c0113b8
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/TuskInstaller.php
@@ -0,0 +1,14 @@
+
+ */
+ class TuskInstaller extends BaseInstaller
+ {
+ protected $locations = array(
+ 'task' => '.tusk/tasks/{$name}/',
+ 'command' => '.tusk/commands/{$name}/',
+ 'asset' => 'assets/tusk/{$name}/',
+ );
+ }
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/UserFrostingInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/UserFrostingInstaller.php
new file mode 100644
index 00000000..fcb414ab
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/UserFrostingInstaller.php
@@ -0,0 +1,9 @@
+ 'app/sprinkles/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/VanillaInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/VanillaInstaller.php
new file mode 100644
index 00000000..24ca6451
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/VanillaInstaller.php
@@ -0,0 +1,10 @@
+ 'plugins/{$name}/',
+ 'theme' => 'themes/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/VgmcpInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/VgmcpInstaller.php
new file mode 100644
index 00000000..7d90c5e6
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/VgmcpInstaller.php
@@ -0,0 +1,49 @@
+ 'src/{$vendor}/{$name}/',
+ 'theme' => 'themes/{$name}/'
+ );
+
+ /**
+ * Format package name.
+ *
+ * For package type vgmcp-bundle, cut off a trailing '-bundle' if present.
+ *
+ * For package type vgmcp-theme, cut off a trailing '-theme' if present.
+ *
+ */
+ public function inflectPackageVars($vars)
+ {
+ if ($vars['type'] === 'vgmcp-bundle') {
+ return $this->inflectPluginVars($vars);
+ }
+
+ if ($vars['type'] === 'vgmcp-theme') {
+ return $this->inflectThemeVars($vars);
+ }
+
+ return $vars;
+ }
+
+ protected function inflectPluginVars($vars)
+ {
+ $vars['name'] = preg_replace('/-bundle$/', '', $vars['name']);
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+
+ protected function inflectThemeVars($vars)
+ {
+ $vars['name'] = preg_replace('/-theme$/', '', $vars['name']);
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WHMCSInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WHMCSInstaller.php
new file mode 100644
index 00000000..b65dbbaf
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WHMCSInstaller.php
@@ -0,0 +1,21 @@
+ 'modules/addons/{$vendor}_{$name}/',
+ 'fraud' => 'modules/fraud/{$vendor}_{$name}/',
+ 'gateways' => 'modules/gateways/{$vendor}_{$name}/',
+ 'notifications' => 'modules/notifications/{$vendor}_{$name}/',
+ 'registrars' => 'modules/registrars/{$vendor}_{$name}/',
+ 'reports' => 'modules/reports/{$vendor}_{$name}/',
+ 'security' => 'modules/security/{$vendor}_{$name}/',
+ 'servers' => 'modules/servers/{$vendor}_{$name}/',
+ 'social' => 'modules/social/{$vendor}_{$name}/',
+ 'support' => 'modules/support/{$vendor}_{$name}/',
+ 'templates' => 'templates/{$vendor}_{$name}/',
+ 'includes' => 'includes/{$vendor}_{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WolfCMSInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WolfCMSInstaller.php
new file mode 100644
index 00000000..cb387881
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WolfCMSInstaller.php
@@ -0,0 +1,9 @@
+ 'wolf/plugins/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WordPressInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WordPressInstaller.php
new file mode 100644
index 00000000..91c46ad9
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/WordPressInstaller.php
@@ -0,0 +1,12 @@
+ 'wp-content/plugins/{$name}/',
+ 'theme' => 'wp-content/themes/{$name}/',
+ 'muplugin' => 'wp-content/mu-plugins/{$name}/',
+ 'dropin' => 'wp-content/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/YawikInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/YawikInstaller.php
new file mode 100644
index 00000000..27f429ff
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/YawikInstaller.php
@@ -0,0 +1,32 @@
+ 'module/{$name}/',
+ );
+
+ /**
+ * Format package name to CamelCase
+ * @param array $vars
+ *
+ * @return array
+ */
+ public function inflectPackageVars($vars)
+ {
+ $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name']));
+ $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']);
+ $vars['name'] = str_replace(' ', '', ucwords($vars['name']));
+
+ return $vars;
+ }
+}
\ No newline at end of file
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ZendInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ZendInstaller.php
new file mode 100644
index 00000000..bde9bc8c
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ZendInstaller.php
@@ -0,0 +1,11 @@
+ 'library/{$name}/',
+ 'extra' => 'extras/library/{$name}/',
+ 'module' => 'module/{$name}/',
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ZikulaInstaller.php b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ZikulaInstaller.php
new file mode 100644
index 00000000..56cdf5da
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/Composer/Installers/ZikulaInstaller.php
@@ -0,0 +1,10 @@
+ 'modules/{$vendor}-{$name}/',
+ 'theme' => 'themes/{$vendor}-{$name}/'
+ );
+}
diff --git a/wp-content/plugins/imagify/vendor/composer/installers/src/bootstrap.php b/wp-content/plugins/imagify/vendor/composer/installers/src/bootstrap.php
new file mode 100644
index 00000000..0de276ee
--- /dev/null
+++ b/wp-content/plugins/imagify/vendor/composer/installers/src/bootstrap.php
@@ -0,0 +1,13 @@
+ array(
+ array('onPostAutoloadDump', 0)
+ ),
+ );
+ }
+
+ public function onPostAutoloadDump(Event $event)
+ {
+ $composerConfig = $event->getComposer()->getConfig();
+ $composerAutoloadDir = "{$composerConfig->get('vendor-dir')}/composer";
+
+ $classLoader = "{$composerAutoloadDir}/ClassLoader.php";
+ $autoloadReal = "{$composerAutoloadDir}/autoload_real.php";
+ $autoloadStatic = "{$composerAutoloadDir}/autoload_static.php";
+
+ $suffix = $composerConfig->get('classloader-suffix') ?: md5(uniqid('', true));
+
+ self::replaceInFiles(
+ array($classLoader, $autoloadReal),
+ '/Composer\\\\Autoload(;|\\\\(?!ComposerStaticInit))/',
+ "Composer\\Autoload{$suffix}\$1"
+ );
+
+ self::replaceInFiles(
+ array($autoloadStatic),
+ array(
+ '/\bClassLoader\b/'
+ => "ClassLoader{$suffix}",
+ '/'.preg_quote("\nnamespace Composer\\Autoload;\n", '/').'/'
+ => "$0\nuse Composer\\Autoload{$suffix}\\ClassLoader as ClassLoader{$suffix};\n\n",
+ )
+ );
+ }
+
+ private static function replaceInFiles(array $files, $search, $replace = null)
+ {
+ if (func_num_args() == 3) {
+ $search = array($search => $replace);
+ }
+
+ foreach ($files as $file) {
+ $contents = file_get_contents($file);
+ $contents = preg_replace(array_keys($search), array_values($search), $contents);
+ file_put_contents($file, $contents);
+ }
+ }
+}
diff --git a/wp-content/plugins/imagify/views/button/compare-images.php b/wp-content/plugins/imagify/views/button/compare-images.php
new file mode 100644
index 00000000..0b37b8a0
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/compare-images.php
@@ -0,0 +1,13 @@
+%7$s',
+ esc_url( $data['url'] ),
+ $data['media_id'],
+ esc_url( $data['backup_url'] ),
+ esc_url( $data['original_url'] ),
+ $data['width'],
+ $data['height'],
+ esc_html__( 'Compare Original VS Optimized', 'imagify' )
+);
diff --git a/wp-content/plugins/imagify/views/button/delete-webp.php b/wp-content/plugins/imagify/views/button/delete-webp.php
new file mode 100644
index 00000000..fed6e26b
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/delete-webp.php
@@ -0,0 +1,31 @@
+build_attributes( $data['atts'] );
+?>
+
+>
+
+
+
+
+print_js_template_in_footer( 'button/processing' );
+}
diff --git a/wp-content/plugins/imagify/views/button/generate-webp.php b/wp-content/plugins/imagify/views/button/generate-webp.php
new file mode 100644
index 00000000..d8239f3f
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/generate-webp.php
@@ -0,0 +1,33 @@
+build_attributes( $data['atts'] );
+?>
+
+>
+
+
+
+
+print_js_template_in_footer( 'button/processing' );
+}
diff --git a/wp-content/plugins/imagify/views/button/optimize-missing-sizes.php b/wp-content/plugins/imagify/views/button/optimize-missing-sizes.php
new file mode 100644
index 00000000..bede0e14
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/optimize-missing-sizes.php
@@ -0,0 +1,39 @@
+build_attributes( $data['atts'] );
+?>
+
+>
+
+ ',
+ ''
+ );
+ ?>
+
+
+print_js_template_in_footer( 'button/processing' );
+}
diff --git a/wp-content/plugins/imagify/views/button/optimize.php b/wp-content/plugins/imagify/views/button/optimize.php
new file mode 100644
index 00000000..89e0a79f
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/optimize.php
@@ -0,0 +1,30 @@
+build_attributes( $data['atts'] );
+?>
+
+>
+
+
+
+print_js_template_in_footer( 'button/processing' );
+}
diff --git a/wp-content/plugins/imagify/views/button/processing.php b/wp-content/plugins/imagify/views/button/processing.php
new file mode 100644
index 00000000..dc9d7bcf
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/processing.php
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+print_js_template_in_footer( 'button/processing' );
diff --git a/wp-content/plugins/imagify/views/button/re-optimize.php b/wp-content/plugins/imagify/views/button/re-optimize.php
new file mode 100644
index 00000000..3e80ba78
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/re-optimize.php
@@ -0,0 +1,46 @@
+build_attributes( $data['atts'] );
+?>
+
+>
+
+
+ ' . esc_html( $level_label ) . ''
+ );
+ ?>
+
+
+
+print_js_template_in_footer( 'button/processing' );
+}
diff --git a/wp-content/plugins/imagify/views/button/refresh-status.php b/wp-content/plugins/imagify/views/button/refresh-status.php
new file mode 100644
index 00000000..af429923
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/refresh-status.php
@@ -0,0 +1,31 @@
+build_attributes( $data['atts'] );
+?>
+
+>
+
+
+
+
+print_js_template_in_footer( 'button/processing' );
+}
diff --git a/wp-content/plugins/imagify/views/button/restore.php b/wp-content/plugins/imagify/views/button/restore.php
new file mode 100644
index 00000000..2daeda9f
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/restore.php
@@ -0,0 +1,31 @@
+build_attributes( $data['atts'] );
+?>
+
+>
+
+
+
+
+print_js_template_in_footer( 'button/processing' );
+}
diff --git a/wp-content/plugins/imagify/views/button/retry-optimize.php b/wp-content/plugins/imagify/views/button/retry-optimize.php
new file mode 100644
index 00000000..12d4a9fb
--- /dev/null
+++ b/wp-content/plugins/imagify/views/button/retry-optimize.php
@@ -0,0 +1,48 @@
+build_attributes( $data['atts'] );
+
+if ( ! empty( $data['error'] ) ) {
+ ?>
+
+ true,
+ 'code' => true,
+ 'em' => true,
+ 'strong' => true,
+ ]
+ );
+ ?>
+
+
+
+>
+
+
+
+print_js_template_in_footer( 'button/processing' );
+}
diff --git a/wp-content/plugins/imagify/views/container/data-actions.php b/wp-content/plugins/imagify/views/container/data-actions.php
new file mode 100644
index 00000000..9d4bd822
--- /dev/null
+++ b/wp-content/plugins/imagify/views/container/data-actions.php
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/modal-payment.php b/wp-content/plugins/imagify/views/modal-payment.php
new file mode 100644
index 00000000..e1ea1b65
--- /dev/null
+++ b/wp-content/plugins/imagify/views/modal-payment.php
@@ -0,0 +1,433 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ' . number_format_i18n( $attachments_number ) . ' '
+ );
+ ?>
+
+
+
+
+
+
+ 0'
+ );
+ ?>
+
+
+
+ 0'
+ );
+ ?>
+
+
+
+
+
+ print_template( 'part-discount-banner' ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 GB
+
+
+
+ ' . number_format_i18n( 5000 ) . ' '
+ );
+ ?>
+
+
+
+
+
+ $
+
+
+
+ 3
+ .99
+
+
+ 3
+ .16
+
+
+
+
+
+
+
+ additional Gb', 'imagify' ),
+ ' '
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3 GB
+
+
+ ' . number_format_i18n( 54000 ) . ' '
+ );
+ ?>
+
+
+
+
+
+ $
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ coupon code use it here:', 'imagify' ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ print_template( 'part-settings-discount-banner' ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ',
+ ' '
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 12.24 %
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 68.36 %
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 86.57 %
+
+
+
+
+
+
+
+
+
+
+
+
+print_template( 'notice-header', array(
+ 'classes' => array( 'imagify-flex-notice-content', 'error' ),
+) );
+
+$views = Imagify_Views::get_instance();
+?>
+
+
+
get_quota_icon(); ?>
+
+
+ ' . $views->get_quota_percent() . '%'
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+print_template( 'notice-footer', array(
+ 'dismissible' => 'almost-over-quota',
+) );
diff --git a/wp-content/plugins/imagify/views/notice-backup-folder-not-writable.php b/wp-content/plugins/imagify/views/notice-backup-folder-not-writable.php
new file mode 100644
index 00000000..26bfb03b
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-backup-folder-not-writable.php
@@ -0,0 +1,20 @@
+print_template( 'notice-header', array(
+ 'classes' => array( 'error' ),
+) );
+
+$backup_path = $this->filesystem->make_path_relative( get_imagify_backup_dir_path( true ) );
+
+if ( $this->filesystem->exists( get_imagify_backup_dir_path() ) ) {
+ /* translators: %s is a file path. */
+ $message = __( 'The backup folder %s is not writable by the server, original images cannot be saved!', 'imagify' );
+} else {
+ /* translators: %s is a file path. */
+ $message = __( 'The backup folder %s cannot be created. Is its parent directory writable by the server? Original images cannot be saved!', 'imagify' );
+}
+
+echo '' . sprintf( $message, "$backup_path" ) . '
';
+
+$this->print_template( 'notice-footer' );
diff --git a/wp-content/plugins/imagify/views/notice-footer.php b/wp-content/plugins/imagify/views/notice-footer.php
new file mode 100644
index 00000000..6b079aba
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-footer.php
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/notice-grid-view.php b/wp-content/plugins/imagify/views/notice-grid-view.php
new file mode 100644
index 00000000..9c5dc938
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-grid-view.php
@@ -0,0 +1,13 @@
+print_template( 'notice-header', array(
+ 'title' => __( 'You\'re missing out!', 'imagify' ),
+) );
+?>
+
+
+print_template( 'notice-footer', array(
+ 'dismissible' => 'grid-view',
+) );
diff --git a/wp-content/plugins/imagify/views/notice-header.php b/wp-content/plugins/imagify/views/notice-header.php
new file mode 100644
index 00000000..f9f170d5
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-header.php
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/notice-http-block-external.php b/wp-content/plugins/imagify/views/notice-http-block-external.php
new file mode 100644
index 00000000..82774987
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-http-block-external.php
@@ -0,0 +1,22 @@
+print_template( 'notice-header', array(
+ 'title' => __( 'The external HTTP requests are blocked!', 'imagify' ),
+ 'classes' => array( 'error' ),
+) );
+?>
+
+ WP_HTTP_BLOCK_EXTERNAL constant in the wp-config.php to block all external HTTP requests.', 'imagify' ); ?>
+
+
+ wp-config.php file so that it works correctly.', 'imagify' ); ?>
+
+
+
+
+
+print_template( 'notice-footer', array(
+ 'dismissible' => 'http-block-external',
+) );
diff --git a/wp-content/plugins/imagify/views/notice-plugins-to-deactivate.php b/wp-content/plugins/imagify/views/notice-plugins-to-deactivate.php
new file mode 100644
index 00000000..5da7368d
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-plugins-to-deactivate.php
@@ -0,0 +1,20 @@
+print_template( 'notice-header', array(
+ 'classes' => array( 'error' ),
+) );
+?>
+
+
+
+print_template( 'notice-footer' );
diff --git a/wp-content/plugins/imagify/views/notice-rating.php b/wp-content/plugins/imagify/views/notice-rating.php
new file mode 100644
index 00000000..9f13d707
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-rating.php
@@ -0,0 +1,36 @@
+print_template( 'notice-header', array(
+ 'classes' => array( 'updated' ),
+) );
+?>
+
+ ',
+ '',
+ number_format_i18n( $data )
+ );
+ ?>
+
+
+ ',
+ ' ',
+ '',
+ ' '
+ );
+ ?>
+
+ âââââ
+
+print_template( 'notice-footer', array(
+ 'dismissible' => 'rating',
+) );
diff --git a/wp-content/plugins/imagify/views/notice-temporary.php b/wp-content/plugins/imagify/views/notice-temporary.php
new file mode 100644
index 00000000..7d07488c
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-temporary.php
@@ -0,0 +1,22 @@
+ $type_notices ) {
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ',
+ ''
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/notice-wp-rocket.php b/wp-content/plugins/imagify/views/notice-wp-rocket.php
new file mode 100644
index 00000000..ce9eb584
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-wp-rocket.php
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+ ';
+ printf(
+ /* translators: 1 is a "bold" tag start, 2 is a pourcentage, 3 is the "bold" tag end, 4 is a coupon code. */
+ esc_html__( '%1$sGet %2$s off%3$s with this coupon code: %4$s', 'imagify' ),
+ '', '20%', ' ', $coupon_code
+ );
+ ?>
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/notice-wrong-api-key.php b/wp-content/plugins/imagify/views/notice-wrong-api-key.php
new file mode 100644
index 00000000..c65ab5d1
--- /dev/null
+++ b/wp-content/plugins/imagify/views/notice-wrong-api-key.php
@@ -0,0 +1,24 @@
+print_template( 'notice-header', array(
+ 'title' => __( 'Your API key isn\'t valid!', 'imagify' ),
+ 'classes' => array( 'error' ),
+) );
+?>
+
+
+ ',
+ '',
+ ' '
+ );
+ ?>
+
+print_template( 'notice-footer', array(
+ 'dismissible' => 'wrong-api-key',
+) );
diff --git a/wp-content/plugins/imagify/views/page-bulk.php b/wp-content/plugins/imagify/views/page-bulk.php
new file mode 100644
index 00000000..80431254
--- /dev/null
+++ b/wp-content/plugins/imagify/views/page-bulk.php
@@ -0,0 +1,231 @@
+
+
+
+ print_template( 'part-bulk-optimization-header' ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '
+ );
+ ?>
+
+
+
+
+
+
+
+
+ %
+
+ '
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
+ ' . esc_html( min( $data['optimized_attachments_percent'], 100 ) ) . '%'
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ plan_label; ?>
+
+
+
+ plan_id ) { ?>
+
+
+
get_quota_icon(); ?>
+
+
+
+ ' . $this->get_quota_percent() . '%'
+ );
+ ?>
+
+
+
+
+
+
+
+
+ get_quota_percent() ) {
+ esc_html_e( 'Oops, It\'s Over!', 'imagify' );
+ } elseif ( $this->get_quota_percent() <= 20 ) {
+ esc_html_e( 'Oops, It\'s almost over!', 'imagify' );
+ } else {
+ esc_html_e( 'You\'re new to Imagify?', 'imagify' );
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ print_template( 'part-bulk-optimization-success' );
+
+ $this->print_template( 'part-bulk-optimization-table', $data );
+
+ // New Feature!
+ if ( ! empty( $data['no-custom-folders'] ) ) {
+ $this->print_template( 'part-bulk-optimization-newbie' );
+ }
+ ?>
+
+
+
+
+ print_template( 'modal-payment' );
+
+ if ( Imagify_Requirements::is_api_key_valid() ) {
+ $display_infos = get_transient( 'imagify_bulk_optimization_infos' );
+
+ ?>
+
+
+
+
+
+
+
+
+
+
+ print_template( 'part-files-list-header' ); ?>
+
+
+
+
+get_option_name();
+$hidden_class = Imagify_Requirements::is_api_key_valid() ? '' : ' hidden';
+$lang = imagify_get_current_lang_in( array( 'de', 'es', 'fr', 'it' ) );
+
+/* Ads notice */
+$notice = 'wp-rocket';
+$user_id = get_current_user_id();
+$notices = get_user_meta( $user_id, '_imagify_ignore_ads', true );
+$notices = $notices && is_array( $notices ) ? array_flip( $notices ) : array();
+$wrapper_class = isset( $notices[ $notice ] ) || defined( 'WP_ROCKET_VERSION' ) ? 'imagify-have-rocket' : 'imagify-dont-have-rocket';
+?>
+
+
+
+
+ print_template( 'part-settings-header' ); ?>
+
+
+
+
+ print_template( 'part-rocket-ad' );
+ $this->print_template( 'modal-settings-infos' );
+ $this->print_template( 'modal-settings-partners-infos' );
+ $this->print_template( 'modal-settings-visual-comparison' );
+ $this->print_template( 'modal-payment' );
+ ?>
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-bulk-optimization-header-row-library.php b/wp-content/plugins/imagify/views/part-bulk-optimization-header-row-library.php
new file mode 100644
index 00000000..b55dda38
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-bulk-optimization-header-row-library.php
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-bulk-optimization-header.php b/wp-content/plugins/imagify/views/part-bulk-optimization-header.php
new file mode 100644
index 00000000..5561d069
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-bulk-optimization-header.php
@@ -0,0 +1,19 @@
+
+
+
â Imagify
+
+
+
+
+
+
+
+
+
+
+
+
+
+ print_template( 'part-documentation-link' ); ?>
+
+
+
+
+
+
+ ' . esc_html( $data['quota'] ) . '%'
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-bulk-optimization-overquota-alert.php b/wp-content/plugins/imagify/views/part-bulk-optimization-overquota-alert.php
new file mode 100644
index 00000000..0830018a
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-bulk-optimization-overquota-alert.php
@@ -0,0 +1,35 @@
+ 'plugin',
+ 'utm_medium' => 'imagify-wp',
+ 'utm_content' => 'over-quota',
+) );
+?>
+
+
+
+
+
+
+ ', ' ' );
+ ?>
+
+
+ ', ' ' );
+ ?>
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-bulk-optimization-spinner.php b/wp-content/plugins/imagify/views/part-bulk-optimization-spinner.php
new file mode 100644
index 00000000..3b427a7f
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-bulk-optimization-spinner.php
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-bulk-optimization-success.php b/wp-content/plugins/imagify/views/part-bulk-optimization-success.php
new file mode 100644
index 00000000..c805166b
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-bulk-optimization-success.php
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ',
+ ' '
+ );
+ ?>
+
+
+
+
+
+
+get( 'optimization_level' );
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ print_template( 'input/selector', [
+ 'current_label' => __( 'Current level:', 'imagify' ),
+ 'name' => 'level[' . $data['group_id'] . ']',
+ 'value' => $default_level,
+ 'values' => [
+ 0 => imagify_get_optimization_level_label( 0, '%ICON% %s' ),
+ 1 => imagify_get_optimization_level_label( 1, '%ICON% %s' ),
+ 2 => imagify_get_optimization_level_label( 2, '%ICON% %s' ),
+ ],
+ ] );
+ ?>
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-bulk-optimization-table.php b/wp-content/plugins/imagify/views/part-bulk-optimization-table.php
new file mode 100644
index 00000000..bfd16550
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-bulk-optimization-table.php
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ get_bulk_instance( $group['context'] )->get_context_data();
+ $group = array_merge( $group, $context_data );
+
+ $this->print_template( 'part-bulk-optimization-table-row-folder-type', $group );
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-bulk-optimization-underscore-file-row-custom-folders.php b/wp-content/plugins/imagify/views/part-bulk-optimization-underscore-file-row-custom-folders.php
new file mode 100644
index 00000000..e21e7cc8
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-bulk-optimization-underscore-file-row-custom-folders.php
@@ -0,0 +1,26 @@
+
+
+
+
+ {{ data.filename }}
+
+
+
+
+ {{ data.label }}
+
+
+ {{ data.originalSizeHuman }}
+ {{ data.newSizeHuman }}
+
+
+
+
+
+
+ {{ data.percentHuman }}
+
+ {{ data.overallSavingHuman }}
+
diff --git a/wp-content/plugins/imagify/views/part-bulk-optimization-underscore-file-row-library.php b/wp-content/plugins/imagify/views/part-bulk-optimization-underscore-file-row-library.php
new file mode 100644
index 00000000..03fef94f
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-bulk-optimization-underscore-file-row-library.php
@@ -0,0 +1,27 @@
+
+
+
+
+ {{ data.filename }}
+
+
+
+
+ {{ data.label }}
+
+
+ {{ data.thumbnailsCount }}
+ {{ data.originalSizeHuman }}
+ {{ data.newSizeHuman }}
+
+
+
+
+
+
+ {{ data.percentHuman }}
+
+ {{ data.overallSavingHuman }}
+
diff --git a/wp-content/plugins/imagify/views/part-discount-banner.php b/wp-content/plugins/imagify/views/part-discount-banner.php
new file mode 100644
index 00000000..bd91342e
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-discount-banner.php
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ get_percent_unconsumed_quota : false;
+ $hidden_class = '';
+
+ if ( ! $user ) {
+ // Lazyload user.
+ Imagify_Assets::get_instance()->localize_script( 'options', 'imagifyUser', array(
+ 'action' => 'imagify_get_user_data',
+ '_wpnonce' => wp_create_nonce( 'imagify_get_user_data' ),
+ ) );
+ }
+} else {
+ $hidden_class = ' hidden';
+}
+?>
+
+
+
+
+
+
+
+
+
+ plan_label ) : ''; ?>
+
+
+
+
+
+
+ get( 'api_key' ) ) {
+ ?>
+
+
+
+
+
+
+ get( 'api_key' ) ? esc_html__( 'API Key', 'imagify' ) : esc_html__( 'Enter Your API Key Below', 'imagify' ); ?>
+
+
+
+
+ â
+
+
+ get( 'api_key' ) ) {
+ ?>
+
+
+
+
+
+
+
+
+
+
+get_active_folders_column( 'path' );
+$themes = array();
+
+if ( $custom_folders ) {
+ $custom_folders = array_combine( $custom_folders, $custom_folders );
+ $custom_folders = array_map( array( 'Imagify_Files_Scan', 'remove_placeholder' ), $custom_folders );
+ $custom_folders = array_map( 'trailingslashit', $custom_folders );
+ $custom_folders = array_filter( $custom_folders, array( 'Imagify_Files_Scan', 'is_path_autorized' ) );
+}
+
+if ( $custom_folders ) {
+ $custom_folders = array_map( array( $this->filesystem, 'make_path_relative' ), $custom_folders );
+ $custom_folders = array_map( 'untrailingslashit', $custom_folders );
+ natcasesort( $custom_folders );
+ $custom_folders = array_map( 'trailingslashit', $custom_folders );
+
+ if ( isset( $custom_folders['{{ROOT}}/'] ) ) {
+ $custom_folders['{{ROOT}}/'] = __( 'Site\'s root', 'imagify' );
+ }
+}
+
+// Current used theme(s).
+if ( ! is_network_admin() ) {
+ $current_theme = wp_get_theme();
+ $themes_not_added = array();
+
+ foreach ( array( $current_theme, $current_theme->parent() ) as $theme ) {
+ if ( ! $theme || ! $theme->exists() ) {
+ continue;
+ }
+
+ $theme_path = trailingslashit( $theme->get_stylesheet_directory() );
+
+ if ( ! Imagify_Files_Scan::is_path_forbidden( $theme_path ) ) {
+ $theme = array(
+ 'name' => $theme->display( 'Name' ),
+ 'path' => Imagify_Files_Scan::add_placeholder( $theme_path ),
+ 'label' => $this->filesystem->make_path_relative( $theme_path ),
+ );
+
+ $themes[ $theme['path'] ] = $theme;
+ $added = false;
+ $rel_path = strtolower( $theme['label'] );
+
+ foreach ( $custom_folders as $path => $label ) {
+ if ( strpos( $rel_path, strtolower( $label ) ) === 0 ) {
+ $added = true;
+ break;
+ }
+ }
+
+ if ( ! $added ) {
+ $themes_not_added[] = $theme['path'];
+ }
+ }
+ }
+
+ $themes_count = count( $themes );
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ' . $theme['name'] . '' );
+ ?>
+
+ $theme ) {
+ $themes[ $path ] = esc_attr( $theme['path'] ) . '#///#' . esc_attr( $theme['label'] );
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $label ) {
+ $this->print_template( 'part-settings-row-custom-folder', array(
+ 'value' => $placeholder,
+ 'label' => $label,
+ ) );
+ }
+ }
+ ?>
+
+
+
+ opening and closing tags. */
+ __( '%1$sSelecting a folder will also optimize images in sub-folders.%2$s The only exception is "Siteâs root": when selected, only images that are directly at the siteâs root will be optimized (sub-folders can be selected separately).', 'imagify' ),
+ '',
+ ' '
+ );
+ ?>
+
+
+
+
+
+
+
+
+
+
';
+?>
+
+
>
+
+ title="">
+
+
+
+
+
+
+
+
+
+
+
+ />
+
+ ">
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-settings-footer.php b/wp-content/plugins/imagify/views/part-settings-footer.php
new file mode 100644
index 00000000..ae1e45cb
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-settings-footer.php
@@ -0,0 +1,28 @@
+
+
+
+ current_user_can( 'bulk-optimize' ) || imagify_get_context( 'custom-folders' )->current_user_can( 'bulk-optimize' );
+
+ if ( $user_can ) {
+ // Submit and go to bulk page.
+ submit_button(
+ esc_html__( 'Save & Go to Bulk Optimizer', 'imagify' ),
+ 'secondary imagify-button-secondary', // Type/classes.
+ 'submit-goto-bulk', // Name (id).
+ true, // Wrap.
+ array() // Other attributes.
+ );
+ }
+ }
+ ?>
+
+
+
+
â Imagify
+
+
+
+
+
+
+
+
+
+
+
+
+ get( 'api_key' ) ) {
+ ?>
+
+ ',
+ ' ',
+ '',
+ ' '
+ );
+ ?>
+
+ ', 5 ); ?>
+
+
+
+ print_template( 'part-documentation-link' ); ?>
+
+get_option_name();
+?>
+
+
+
+
+ field_checkbox(
+ [
+ 'option_name' => 'resize_larger',
+ 'label' => __( 'Resize larger images', 'imagify' ),
+ 'attributes' => [
+ 'aria-describedby' => 'describe-resize_larger',
+ ],
+ ]
+ );
+ ?>
+
+
+
+ get( 'resize_larger_w' );
+ printf(
+ /* translators: 1 is a text input for a number of pixels (don't use %d). */
+ esc_html__( 'to maximum %s pixels width', 'imagify' ),
+ ' '
+ );
+ ?>
+
+
+
+
+
+ ' . esc_html__( 'Resizing is done on upload or during optimization.', 'imagify' ) . '';
+ } else {
+ esc_html_e( 'Resizing is done only during optimization.', 'imagify' );
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ', ''
+ );
+ ?>
+
+
+
+
+
+
+ field_checkbox_list(
+ [
+ 'option_name' => 'disallowed-sizes',
+ 'legend' => __( 'Choose the sizes to optimize', 'imagify' ),
+ 'values' => Imagify_Settings::get_thumbnail_sizes(),
+ 'reverse_check' => true,
+ ]
+ );
+ ?>
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/imagify/views/part-settings-webp.php b/wp-content/plugins/imagify/views/part-settings-webp.php
new file mode 100644
index 00000000..86c71ee0
--- /dev/null
+++ b/wp-content/plugins/imagify/views/part-settings-webp.php
@@ -0,0 +1,168 @@
+
+
+
+
+
+ field_checkbox( [
+ 'option_name' => 'convert_to_webp',
+ 'label' => __( 'Create webp versions of images', 'imagify' ),
+ 'attributes' => [
+ 'aria-describedby' => 'describe-convert_to_webp',
+ ],
+ ] );
+ ?>
+
+
+ field_checkbox( [
+ 'option_name' => 'display_webp',
+ 'label' => __( 'Display images in webp format on the site', 'imagify' ),
+ ] );
+ ?>
+
+
+ field_radio_list( [
+ 'option_name' => 'display_webp_method',
+ 'values' => [
+ 'rewrite' => __( 'Use rewrite rules', 'imagify' ),
+ /* translators: 1 and 2 are
tag opening and closing. */
+ 'picture' => sprintf( __( 'Use <picture> tags %1$s(preferred)%2$s', 'imagify' ), '', ' ' ),
+ ],
+ 'attributes' => [
+ 'aria-describedby' => 'describe-convert_to_webp',
+ ],
+ ] );
+ ?>
+
+
+ get_cdn_source();
+
+ if ( 'option' !== $cdn_source['source'] ) {
+ if ( 'constant' === $cdn_source['source'] ) {
+ printf(
+ /* translators: 1 is an URL, 2 is a php constant name. */
+ esc_html__( 'Your CDN URL is set to %1$s by the constant %2$s.', 'imagify' ),
+ '' . esc_url( $cdn_source['url'] ) . '',
+ '' . esc_html( $cdn_source['name'] ) . ''
+ );
+ } elseif ( ! empty( $cdn_source['name'] ) ) {
+ printf(
+ /* translators: 1 is an URL, 2 is a plugin name. */
+ esc_html__( 'Your CDN URL is set to %1$s by %2$s.', 'imagify' ),
+ '' . esc_url( $cdn_source['url'] ) . '',
+ '' . esc_html( $cdn_source['name'] ) . ''
+ );
+ } else {
+ printf(
+ /* translators: %s is an URL. */
+ esc_html__( 'Your CDN URL is set to %1$s by filter.', 'imagify' ),
+ '' . esc_url( $cdn_source['url'] ) . ''
+ );
+ }
+
+ $settings->field_hidden( [
+ 'option_name' => 'cdn_url',
+ 'current_value' => $cdn_source['url'],
+ ] );
+ } else {
+ $settings->field_text_box( [
+ 'option_name' => 'cdn_url',
+ 'label' => __( 'If you use a CDN, specify the URL:', 'imagify' ),
+ 'attributes' => [
+ 'size' => 30,
+ 'placeholder' => __( 'https://cdn.example.com', 'imagify' ),
+ ],
+ ] );
+ }
+ ?>
+
+
+
+
+
+ get_file_path( true );
+
+ if ( $conf_file_path ) {
+ printf(
+ /* translators: 1 is a file name, 2 is a tag opening, 3 is the tag closing. */
+ esc_html__( 'The first option adds rewrite rules to your siteâs configuration file (%1$s) and does not alter your pages code. %2$sThis does not work with CDN though.%3$s', 'imagify' ),
+ '' . esc_html( $conf_file_path ) . '',
+ '',
+ ' '
+ );
+
+ echo ' ';
+ }
+
+ printf(
+ /* translators: 1 and 2 are HTML tag names, 3 is a tag opening, 4 is the tag closing. */
+ esc_html__( 'The second option replaces the %1$s tags with %2$s tags. %3$sThis is the preferred solution but some themes may break%4$s, so make sure to verify that everything seems fine.', 'imagify' ),
+ '<img>',
+ '<picture>',
+ '',
+ ' '
+ );
+
+ echo ' ';
+
+ /**
+ * Add more information about webp.
+ *
+ * @since 1.9
+ * @author Grégory Viguier
+ */
+ do_action( 'imagify_settings_webp_info' );
+ ?>
+
+
+
+ get_cached_stat();
+
+ if ( $count ) {
+ ?>
+
+
+
+
+
+
+img, #wp-admin-bar-wp-rocket #wp-admin-bar-preload-cache .ab-item>img{max-width: 16px; max-height: 11px}
+.rocket-promo-bubble {
+ display: inline-block;
+ vertical-align: top;
+ box-sizing: border-box;
+ margin: 1px 0 -1px 2px;
+ padding: 0 5px;
+ min-width: 18px;
+ height: 18px;
+ border-radius: 9px;
+ background-color: #00A66B;
+ color: #fff;
+ font-size: 11px;
+ line-height: 1.6;
+ text-align: center;
+ z-index: 26;
+}
diff --git a/wp-content/plugins/wp-rocket/assets/css/wpr-admin-rtl.css b/wp-content/plugins/wp-rocket/assets/css/wpr-admin-rtl.css
new file mode 100644
index 00000000..e2acc455
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/css/wpr-admin-rtl.css
@@ -0,0 +1 @@
+.wpr-wrap{margin:0 -20px 0 0}#hs-beacon iframe:nth-child(1){right:inherit !important;left:18px !important}#hs-beacon iframe:nth-child(2){right:inherit !important;left:6px !important}.wpr-Page-col--fixed{margin-left:0;margin-right:24px}@media (max-width: 1239px){.wpr-Page-col--fixed{margin-right:0}}.wpr-Content{border:1px solid #E0E4E9;border-right:none}.wpr-Content-tips{right:unset;left:24px}.wpr-Sidebar-notice{border:1px solid #E8EBEE;border-right:2px solid #1EADBF}.wpr-Sidebar .wpr-Sidebar-info h4{padding-left:inherit;padding-right:56px}.wpr-menuItem{padding:16px 20px 18px 44px}.wpr-menuItem:before{right:inherit !important;left:18px !important}.wpr-menuItem:after{border-width:12px 0 12px 10px;border-color:transparent transparent transparent #fff;left:0;right:inherit;transform:translateX(-12px)}.wpr-menuItem:hover{transform:translateX(0)}.wpr-menuItem.wpr-subMenuItem{padding:10px 25px 8px 20px}.wpr-sectionHeader:before{left:inherit;right:0}.wpr-sectionHeader .wpr-title1:before{margin-left:24px;margin-right:0}.wpr-optionHeader .wpr-title2{padding-right:0;padding-left:40px}.wpr-infoAction:before{right:-26px}.wpr-fieldWarning{padding:16px 56px 24px 16px}.wpr-fieldWarning:after{left:inherit;right:20px}.wpr-fieldWarning:before{left:inherit;right:-16px}.wpr-fieldWarning-title:before{left:inherit;right:-40px}.wpr-checkbox{padding-left:0;padding-right:32px}.wpr-checkbox [type="checkbox"]:not(:checked),.wpr-checkbox [type="checkbox"]:checked{right:-9999px}.wpr-checkbox [type="checkbox"]:not(:checked)+label:before,.wpr-checkbox [type="checkbox"]:checked+label:before{left:inherit;right:0}.wpr-checkbox [type="checkbox"]:not(:checked)+label:after,.wpr-checkbox [type="checkbox"]:checked+label:after{left:inherit;right:2px}.wpr-radio{padding-left:0;padding-right:88px}.wpr-radio [type="checkbox"]:not(:checked),.wpr-radio [type="checkbox"]:checked{right:-9999px}.wpr-radio [type="checkbox"]:not(:checked)+label:before,.wpr-radio [type="checkbox"]:checked+label:before{left:inherit;right:0}.wpr-radio [type="checkbox"]:not(:checked)+label:after,.wpr-radio [type="checkbox"]:checked+label:after{left:inherit;right:3px}.wpr-radio [type="checkbox"]:checked+label:after{right:33px}.wpr-radio [type="checkbox"]:checked+label .wpr-radio-ui,.wpr-radio [type="checkbox"]:not(:checked)+label .wpr-radio-ui:before,.wpr-radio [type="checkbox"]:checked+label .wpr-radio-ui:after{right:4px}.wpr-radio [type="checkbox"]:not(:checked)+label .wpr-radio-ui:before{right:27px}.wpr-radio--reverse{padding-left:0;padding-right:72px}.wpr-radio--reverse [type="checkbox"]:not(:checked)+label:before,.wpr-radio--reverse [type="checkbox"]:checked+label:before{right:0;left:inherit}.wpr-radio--reverse [type="checkbox"]:not(:checked)+label:after,.wpr-radio--reverse [type="checkbox"]:checked+label:after{right:33px;left:inherit}.wpr-radio--reverse [type="checkbox"]:checked+label:after{right:3px;left:inherit}.wpr-radio--reverse [type="checkbox"]:checked+label .wpr-radio-ui,.wpr-radio--reverse [type="checkbox"]:not(:checked)+label .wpr-radio-ui:before,.wpr-radio--reverse [type="checkbox"]:checked+label .wpr-radio-ui:after{right:15px;left:inherit}.wpr-radio--reverse [type="checkbox"]:not(:checked)+label .wpr-radio-ui:before{right:6px;left:inherit}.wpr-multiple .wpr-button{margin-right:16px;margin-left:0}.wpr-multiple-close{margin-right:0;margin-left:16px}.wpr-addon .wpr-flex>div{text-align:right}.wpr-addon .wpr-addon-text{margin-left:inherit;margin-right:32px}@media (max-width: 1239px){.wpr-addon .wpr-addon-text{margin-right:16px}}@media (max-width: 1083px){.wpr-addon .wpr-addon-text{margin-right:32px}}@media (max-width: 783px){.wpr-addon .wpr-addon-text{margin-right:0}}.wpr-tools-col:first-child{padding-right:72px;padding-left:24px}.wpr-tools-col:last-child{text-align:left}@media (max-width: 783px){.wpr-tools-col:last-child{text-align:right}}.wpr-tools-label:before{left:inherit;right:0}.wpr-field .wpr-flex--egal>div:last-child{text-align:left}.wpr-field-list li:before{display:inline-block;margin-right:0;margin-left:8px}.wpr-field--split{padding-right:0}.wpr-field--split+.wpr-field--split{padding-left:0;padding-right:16px}.wpr-field--children{padding-left:0;padding-right:32px}.wpr-field--children.wpr-field--textarea{padding-left:80px;padding-right:32px}@media (max-width: 1239px){.wpr-field--children.wpr-field--textarea{padding-left:32px;padding-right:0}}@media (max-width: 783px){.wpr-field--children.wpr-field--textarea{padding-left:0}}.wpr-field--checkbox .wpr-field-description{margin-left:0;margin-right:32px}.wpr-field--radio .wpr-field-description{margin-left:0;margin-right:88px}.wpr-adblock img{margin-right:0;margin-left:16px}.wpr-adblock-close{right:inherit;left:24px}.wpr-notice{background-position:10% bottom}.wpr-notice-container{padding:24px 40px 24px 25%}.wpr-notice-close{right:inherit;left:24px}
diff --git a/wp-content/plugins/wp-rocket/assets/css/wpr-admin.css b/wp-content/plugins/wp-rocket/assets/css/wpr-admin.css
new file mode 100644
index 00000000..77053a1c
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/css/wpr-admin.css
@@ -0,0 +1 @@
+h1,h2,h3,h4{color:currentColor;margin:0;font-weight:normal}button{padding:0;border:none;background:none;cursor:pointer}a{color:currentColor;transition:color 200ms ease-out;-webkit-transition:color 200ms ease-out}a:hover{color:currentColor}input[type=submit]{cursor:pointer;border:none}a:active,button:active{outline:none}a:focus,button:focus{color:currentColor;box-shadow:none}.wpr-wrap{padding:16px;margin:0 0 0 -20px}@media (max-width: 783px){.wpr-wrap{padding:0;margin:0 0 0 -10px}}.wpr-body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-interpolation-mode:nearest-neighbor;image-rendering:optimizeQuality;text-rendering:optimizeLegibility;display:flex;color:#121116;font-size:.875rem;line-height:1.5}.wpr-body *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@media (max-width: 783px){#hs-beacon{display:none !important}}.wpr-u-flex{display:flex;align-items:center;justify-content:center}@font-face{font-family:'wpr-icomoon';src:url("../fonts/icomoon.eot");src:url("../fonts/icomoon.eot?#iefix") format("embedded-opentype"),url("../fonts/icomoon.woff") format("woff"),url("../fonts/icomoon.ttf") format("truetype"),url("../fonts/icomoon.svg#icomoon") format("svg");font-weight:normal;font-style:normal}[class^="wpr-icon-"]:before,[class*=" wpr-icon-"]:after,[class^="wpr-icon-"]:after,[class*=" wpr-icon-"]:before,[id^="wpr-nav-"]:before,[id*=" wpr-nav-"]:after,[id^="wpr-nav-"]:after,[id*=" wpr-nav-"]:before{font-family:'wpr-icomoon';speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[class^="wpr-icon-"] span.hidden,[class*=" wpr-icon-"] span.hidden{display:inline-block;height:0;width:0;overflow:hidden}.wpr-icon-chevron-right:before{content:"\e900"}.wpr-icon-chevron-left:before{content:"\e900";transform:rotate(180deg)}.wpr-icon-chevron-down:before{content:"\e901";transform:scale(0.6)}.wpr-icon-chevron-up:before{content:"\e902";top:50%;transform:translateY(-50%) scale(0.6)}.wpr-icon-rollback:before{content:"\e903"}.wpr-icon-addon:before,.wpr-addonSubMenuItem:before{content:"\e904"}.wpr-icon-addons:before,#wpr-nav-addons:before{content:"\e905"}.wpr-icon-book:before{content:"\e906"}.wpr-icon-cdn:before,#wpr-nav-page_cdn:before{content:"\e907"}.wpr-icon-database:before,#wpr-nav-database:before{content:"\e908"}.wpr-icon-export:before{content:"\e909"}.wpr-icon-files:before,#wpr-nav-cache:before{content:"\e90a"}.wpr-icon-help:before{content:"\e90b"}.wpr-icon-home:before,#wpr-nav-dashboard:before{content:"\e90c"}.wpr-icon-import:before{content:"\e90d"}.wpr-icon-important:before{content:"\e90e"}.wpr-icon-information:before{content:"\e90f"}.wpr-icon-information2:before{content:"\e910"}.wpr-icon-interrogation:before{content:"\e911"}.wpr-icon-media:before,#wpr-nav-media:before{content:"\e912"}.wpr-icon-plus:before{content:"\e913"}.wpr-icon-refresh:before,#wpr-nav-preload:before{content:"\e914"}.wpr-icon-rules:before,#wpr-nav-advanced_cache:before{content:"\e915"}.wpr-icon-stack:before,#wpr-nav-file_optimization:before{content:"\e916"}.wpr-icon-tools:before,#wpr-nav-tools:before{content:"\e917"}.wpr-icon-trash:before{content:"\e918"}.wpr-icon-user:before{content:"\e919"}.wpr-icon-check:before{content:"\e920"}.wpr-icon-check2:before{content:"\e921"}.wpr-icon-close:before{content:"\e922"}.wpr-icon-heartbeat:before,#wpr-nav-heartbeat:before{content:url("../img/heartbeat.svg")}.wpr-icon-heartbeat-hover:before,#wpr-nav-heartbeat:hover:before,#wpr-nav-heartbeat.isActive:before{content:url("../img/heartbeat-hover.svg")}.wpr-icon-imagify:before,#wpr-nav-imagify:before{content:url("../img/imagify.svg")}.wpr-icon-imagify-hover:before,#wpr-nav-imagify:hover:before,#wpr-nav-imagify.isActive:before{content:url("../img/imagify-hover.svg")}.wpr-icon-tutorial:before,#wpr-nav-tutorials:before{content:url("../img/play.svg")}.wpr-icon-tutorial-hover:before,#wpr-nav-tutorials:hover:before,#wpr-nav-tutorials.isActive:before{content:url("../img/play-hover.svg")}.wpr-icon-tutorial-alt:before{content:url("../img/play-alt.svg")}.wpr-title1{font-size:1.625rem;line-height:1;font-weight:600;letter-spacing:0.01em}.wpr-title2{font-size:1rem;line-height:1.5;font-weight:bold;letter-spacing:-0.02em}.wpr-title3,.wpr-field--radio label,.wpr-select select,.wpr-select label{font-size:.875rem;line-height:1.71429;font-weight:bold;letter-spacing:-0.011em}.wpr-Header{display:flex;flex-direction:column;flex:0 0 225px}@media (max-width: 783px){.wpr-Header{flex:0 0 50px}}.wpr-Header-logo{padding:32px 0 24px;text-align:center}@media (max-width: 783px){.wpr-Header-logo{padding:16px 0 8px}}@media (max-width: 783px){.wpr-Header-logo-desktop{display:none}}.wpr-Header-logo-mobile{display:none}@media (max-width: 783px){.wpr-Header-logo-mobile{display:inline-block}}.wpr-Header-footer{margin-top:auto;padding:48px 20px 0;font-size:.6875rem;line-height:4.36364;color:#666;opacity:0.6;font-weight:bold}@media (max-width: 783px){.wpr-Header-footer{display:none}}.wpr-Sidebar{position:relative;display:none;flex:0 0 290px;padding:24px 16px}@media (max-width: 1239px){.wpr-Sidebar{flex:0 0 260px}}@media (max-width: 1083px){.wpr-Sidebar{display:none !important}}.wpr-Sidebar-title{margin-bottom:32px}.wpr-Sidebar-notice{padding:8px 16px;margin-bottom:16px;background:#fff;border:1px solid #E8EBEE;border-left:2px solid #1EADBF;border-radius:0 3px 3px 0;color:#666}.wpr-Sidebar-notice p{margin:0}.wpr-Sidebar-notice-link{display:inline-block;margin-top:8px;font-size:.6875rem;line-height:1.81818;color:#02707F;letter-spacing:-0.05em;text-transform:uppercase;text-decoration:none;font-weight:bold}.wpr-Sidebar-notice-link:hover,.wpr-Sidebar-notice-link:focus{color:#40BACB}.wpr-Sidebar-info{padding:16px;background:#EBFAF5;margin-bottom:16px;border-radius:3px}.wpr-Sidebar-info h4{padding-left:48px;font-weight:500}.wpr-Sidebar-info p{margin:8px 0 0;font-size:.6875rem;line-height:1.45455;color:#666}.wpr-Sidebar-info i{position:absolute;display:block;margin-top:-1px;width:40px;height:40px;color:#00A66B;font-size:1.0625rem;line-height:2.35294;background:#C6F0DE;border-radius:3px;text-align:center}.wpr-Content{position:relative;background:#fff;padding:32px 24px;flex:1 1 auto;max-width:calc(960px + 270px)}@media (max-width: 783px){.wpr-Content{padding:24px 16px}}.wpr-Content form>input:last-child{margin-top:24px;color:#fff !important}.wpr-Content.isNotFull{max-width:960px}.wpr-Content-tips{position:absolute;top:48px;right:24px;font-weight:bold;color:#666}@media (max-width: 1083px){.wpr-Content-tips{display:none !important}}.wpr-Page{margin-bottom:32px}.wpr-Page-row{display:flex;flex-direction:row}@media (max-width: 1239px){.wpr-Page-row{flex-direction:column}}.wpr-Page-col{flex:1 1 auto}.wpr-Page-col--fixed{margin-left:24px;flex:0 0 325px}@media (max-width: 1239px){.wpr-Page-col--fixed{margin-left:0}}.wpr-Page#dashboard #wpr-action-refresh_account:before{transition:all 200ms ease-out;opacity:1;transform:translateY(0)}.wpr-Page#dashboard #wpr-action-refresh_account.wpr-isLoading:before{animation:loading 1.2s infinite}.wpr-Page#dashboard #wpr-action-refresh_account.wpr-isHidden:before{opacity:0}.wpr-Page#dashboard #wpr-action-refresh_account.wpr-isShown:before{opacity:1}@keyframes loading{from{transform:rotate(0)}to{transform:rotate(360deg)}}.wpr-Page#dashboard .wpr-documentation{margin-top:98px;padding:43px 16px}@media (max-width: 1239px){.wpr-Page#dashboard .wpr-documentation{margin-top:40px}}.wpr-Page#dashboard .wpr-documentation .wpr-button{margin-top:8px}.wpr-Page#dashboard .wpr-documentation i{font-size:3.375rem;line-height:1}.wpr-Page#dashboard .wpr-radio{padding-left:72px}.wpr-Page#dashboard .wpr-field--radio{padding:16px 8px}.wpr-Page#dashboard .wpr-field--radio:first-child{padding-top:0}.wpr-Page#dashboard .wpr-field--radio:last-child{padding-bottom:0}.wpr-Page#dashboard .wpr-field--radio .wpr-field-description{font-style:normal;color:#666;margin-left:72px}.wpr-Page#dashboard .wpr-field-account{padding-bottom:0}.wpr-Page#dashboard .wpr-infoAccount{font-weight:bold;margin-left:8px;color:#444}.wpr-Page#dashboard .wpr-infoAccount:before{content:"";position:relative;display:inline-block;width:13px;height:13px;background:#E0E4E9;border-radius:50%;color:#fff;margin-right:6px;text-align:center;top:2px;font-size:.5rem;line-height:1.625}.wpr-Page#dashboard .wpr-infoAccount.wpr-isValid{color:#00A66B}.wpr-Page#dashboard .wpr-infoAccount.wpr-isValid:before{content:"\e920";font-family:'wpr-icomoon';speak:none;background:#3ECE9D;top:-1px}.wpr-Page#dashboard .wpr-infoAccount.wpr-isInvalid{color:#D60E5B}.wpr-Page#dashboard .wpr-infoAccount.wpr-isInvalid:before{content:"!";font-weight:bold;font-size:.625rem;line-height:1.3;speak:none;background:#D33F49;top:-1px}.wpr-Page#dashboard #wpr-account-data:before{content:none}.wpr-Page#tools #wpr-action-rocket_enable_mobile_cpcss:before{transition:all 200ms ease-out;opacity:1;transform:translateY(0)}.wpr-Page#tools #wpr-action-rocket_enable_mobile_cpcss.wpr-isLoading:before{animation:loading 1.2s infinite}.wpr-Popin{display:none;position:fixed;width:772px;height:auto;top:50%;left:50%;background:#fff;border-radius:3px;transform:translateX(-50%) translateY(-50%);z-index:100000}.wpr-Popin-overlay{display:none;position:fixed;opacity:0;width:100%;height:100%;top:0;left:0;background:rgba(0,0,0,0.8);z-index:99999}.wpr-Popin-header{display:flex;align-items:center;justify-content:space-between;height:64px;padding:0 32px;background:#2D1656;color:#fff;font-weight:600}.wpr-Popin-close{color:#665090;font-size:1.5rem;line-height:1;transition:color 200ms ease-out;-webkit-transition:color 200ms ease-out}.wpr-Popin-close:hover,.wpr-Popin-close:focus{color:#fff;outline:none}.wpr-Popin-content{padding:8px 32px;color:#666}.wpr-Popin-flex{display:flex;flex-direction:row;align-items:center}.wpr-Popin-flex div{margin-left:32px}.wpr-Popin p{margin:16px 0}.wpr-Popin .wp-rocket-data-table{padding:12px 24px;background:#F2F3F6 !important;border:none}.wpr-Popin .wp-rocket-data-table td{width:50%;color:#121116;padding:8px 0;padding-left:4px;border-bottom:1px solid #c2cad4}.wpr-Popin .wp-rocket-data-table td:not(.column-primary){font-family:"Monaco";font-size:.75rem;line-height:1.66667;color:#666;letter-spacing:-0.01em}.wpr-Popin .wp-rocket-data-table tr{background:#F2F3F6;border-bottom:1px solid #E0E4E9}.wpr-Popin .wp-rocket-data-table tr:last-child td{border-bottom:none}.wpr-Popin .wp-rocket-data-table strong{font-weight:500}.wpr-Popin .wp-rocket-data-table em{font-style:normal}.wpr-Popin .wp-rocket-data-table code{padding:0;margin:0;background:transparent}.wpr-rocketcdn-cta-small{border-radius:5px;margin:24px 0;padding:16px}.wpr-rocketcdn-cta-small.wpr-isHidden{display:none}.wpr-rocketcdn-cta-small .notice-title{font-weight:700}.wpr-rocketcdn-cta-small .wpr-flex{display:flex;justify-content:space-between;align-items:center}@media (max-width: 783px){.wpr-rocketcdn-cta-small .wpr-flex{text-align:start;flex-direction:column}}.wpr-rocketcdn-cta{margin:10px 0;position:relative}.wpr-rocketcdn-cta.wpr-isHidden{display:none}.wpr-rocketcdn-cta-close{position:absolute;top:16px;right:16px;background:transparent;border:0;color:rgba(255,255,255,0.5)}.wpr-rocketcdn-cta-close--no-promo{position:absolute;top:16px;right:16px;background:transparent;border:0;color:rgba(0,0,0,0.5)}.wpr-rocketcdn-cta-close--no-promo:before{content:"\2715";font-size:2rem;line-height:0.5}.wpr-rocketcdn-cta-close:before{content:"\2715";font-size:2rem;line-height:0.5}.wpr-rocketcdn-cta .wpr-rocketcdn-promo{background:#F56640;border-top-left-radius:2px;border-top-right-radius:2px;color:#fff;padding:16px 48px 16px 16px}.wpr-rocketcdn-cta .wpr-rocketcdn-promo-date{margin:0}.wpr-rocketcdn-cta-subtitle{color:#444;margin-top:0;font-size:1rem;line-height:1.5}.wpr-rocketcdn-cta-content{background:#F9FAFB;border-top:1px solid #E8EBEE;border-left:1px solid #E8EBEE;border-right:1px solid #E8EBEE;padding:16px}.wpr-rocketcdn-cta-content--no-promo{border-top-left-radius:2px;border-top-right-radius:2px;background:#F9FAFB;border-top:1px solid #E8EBEE;border-left:1px solid #E8EBEE;border-right:1px solid #E8EBEE;padding:16px}.wpr-rocketcdn-cta .wpr-flex{display:flex;justify-content:space-between;align-items:center}@media (max-width: 783px){.wpr-rocketcdn-cta .wpr-flex{text-align:start;flex-direction:column}}.wpr-rocketcdn-cta .wpr-rocketcdn-features{border-right:2px solid #cdd1d5;margin:0;padding-right:16px}@media (max-width: 783px){.wpr-rocketcdn-cta .wpr-rocketcdn-features{border-right:none}}.wpr-rocketcdn-cta .wpr-rocketcdn-pricing{align-items:center;display:flex;flex-direction:column;padding:8px 16px;width:calc( 100% / 3)}@media (max-width: 783px){.wpr-rocketcdn-cta .wpr-rocketcdn-pricing{width:auto}}.wpr-rocketcdn-cta .wpr-rocketcdn-pricing-regular{color:#72777C;margin-bottom:8px}.wpr-rocketcdn-cta .wpr-rocketcdn-pricing-current{margin-bottom:16px}.wpr-rocketcdn-cta .wpr-rocketcdn-feature{margin:16px 0;min-height:30px;padding-left:62px;position:relative}.wpr-rocketcdn-cta .wpr-rocketcdn-feature:before{position:absolute;top:50%;left:16px;transform:translateY(-50%)}.wpr-rocketcdn-cta .wpr-rocketcdn-bandwidth:before{content:url(../img/bandwidth.svg)}.wpr-rocketcdn-cta .wpr-rocketcdn-configuration:before{content:url(../img/configuration.svg)}.wpr-rocketcdn-cta .wpr-rocketcdn-automatic:before{content:url(../img/automatic.svg)}.wpr-rocketcdn-cta-footer{background:#72777C;border-bottom-left-radius:2px;border-bottom-right-radius:2px;color:#fff;font-weight:700;padding:8px;text-align:center;text-transform:uppercase;font-size:.6875rem;line-height:1.81818}.wpr-rocketcdn-cta-footer a{text-decoration:none}.wpr-rocketcdn-cta-footer a:before{content:"\00a1";border:1px solid #fff;color:#fff;margin-right:8px;width:18px;height:18px;display:inline-block;border-radius:18px;font-size:.875rem;line-height:1.14286;font-style:italic}.wpr-rocketcdn-subscription{text-align:end}.wpr-rocketcdn-subscription .wpr-rocketcdn-open{color:#666;text-decoration:underline}.wpr-license-upgrade-button{font-weight:bold;text-decoration:underline}.wpr-license-upgrade-button:hover{text-decoration:none}.wpr-field.wpr-field-account .wpr-flex{align-items:flex-start}.wpr-infoAccount-License{flex:1 0 60%;margin-right:16px}@media (max-width: 783px){.wpr-field.wpr-field-account .wpr-flex>div{width:100%}}.wpr-field.wpr-field-account .wpr-flex>div:last-child{text-align:right}@media (max-width: 783px){.wpr-field.wpr-field-account .wpr-flex>div:last-child{text-align:left}}.wpr-Popin-Upgrade .wpr-Popin-content{padding-bottom:32px}.wpr-Popin-Upgrade .wpr-Popin-flex{justify-content:space-between}.wpr-Popin-Upgrade .wpr-Popin-flex>div{align-items:center;border:1px solid #DADADA;border-radius:24px;display:flex;flex-direction:column;margin:0 16px 0 0;padding:24px;text-align:center;width:50%}.wpr-Popin-Upgrade .wpr-Popin-flex>div:last-child{margin-right:0}@media (max-width: 783px){.wpr-Popin-Upgrade .wpr-Popin-flex>div{margin:0;width:100%}}.wpr-Upgrade-Plus .wpr-upgrade-title::before{content:url(../img/plus.svg);display:block;width:117px;height:31px;top:0;position:absolute;left:50%;transform:translateX(-50%)}.wpr-Upgrade-Infinite .wpr-upgrade-title::before{content:url(../img/infinite.svg);display:block;width:48px;height:31px;top:0;position:absolute;left:50%;transform:translateX(-50%)}div.wpr-upgrade-saving{background:#FFD147;border-radius:44px;color:#121116;font-weight:bold;margin:0 0 24px 0;padding:8px 16px;text-align:center}.wpr-upgrade-title{color:#F56F46;font-size:1.875rem;line-height:1.2;margin-bottom:16px;padding-top:55px;position:relative}div.wpr-upgrade-prices{color:#121116;font-size:3rem;line-height:1;font-weight:bold;margin:0 0 16px 0}.wpr-upgrade-price-symbol{font-size:1.875rem;line-height:1;vertical-align:super}.wpr-upgrade-price-regular{color:#72777C;font-size:1rem;line-height:1;vertical-align:top}div.wpr-upgrade-websites{color:#121116;font-size:.875rem;line-height:1;font-weight:bold;margin:0 0 24px 0}.wpr-upgrade-link{background:#fff;border:1px solid #F56F46;border-radius:800px;color:#F56F46;display:block;font-size:1rem;line-height:1.125;font-weight:bold;padding:16px 24px;text-decoration:none}.wpr-upgrade-link:hover{background:#F56F46;color:#fff}.wpr-upgrade-link::after{content:"\2192";font-weight:normal;margin-left:8px}.rocket-promo-banner{background:#FFD147;display:flex;justify-content:space-around;margin-top:16px;padding:24px;position:relative}@media (max-width: 783px){.rocket-promo-banner{flex-flow:column}}.rocket-promo-banner>div{display:flex;flex-flow:column;width:50%}@media (max-width: 783px){.rocket-promo-banner>div{width:100%}}.rocket-promo-title{font-weight:bold;margin-bottom:24px}.rocket-promo-discount{background:#fff;border-radius:44px;display:inline-block;margin-right:8px;padding:8px 16px;text-transform:uppercase}.rocket-promo-message,.rocket-promo-deal{font-size:1rem;line-height:1.5;margin-bottom:0}.rocket-promo-deal{margin-top:8px}.rocket-promo-cta-block{align-items:center;margin-right:24px}.rocket-promo-countdown{display:flex;flex-flow:row wrap;width:66%}.rocket-promo-countdown>.rocket-countdown-item{background:#fff;border-radius:8px;flex:1;margin-right:8px;padding:8px;text-align:center}.rocket-promo-countdown>.rocket-countdown-item>.rocket-countdown-value{display:block;font-size:1.5rem;line-height:1;font-weight:bold}.rocket-promo-cta{background:#172153;border-radius:44px;color:#fff;font-weight:bold;padding:16px 32px}.rocket-renewal-banner{background:#FFD147;display:flex;flex-flow:row wrap;align-items:center;justify-content:space-evenly;margin-top:16px;padding:8px}.rocket-renew-message{margin:0 16px}.rocket-renew-message>p{font-size:.875rem;line-height:1.5}.rocket-expired-message>p{font-size:.875rem;line-height:1.5;padding-left:80px}.rocket-expired-title{font-size:1.375rem;line-height:1.5;font-weight:bold}.rocket-expired-title::before{content:url(../img/warning.svg);display:inline-block;height:48px;width:63px;margin-right:17px;vertical-align:middle}.rocket-expired-cta-container{justify-content:center;align-items:center}.rocket-renew-cta{display:block;background:#172153;border-radius:44px;color:#fff;font-size:1rem;line-height:1.125;font-weight:bold;padding:16px 24px;text-decoration:none}.rocket-renew-cta:hover,.rocket-renew-cta:active,.rocket-renew-cta:focus{color:#fff}.rocket-renew-cta::after{content:"\2192";font-weight:normal;margin-left:8px}.wpr-menuItem{position:relative;display:block;padding:16px 44px 18px 20px;text-decoration:none;color:#121116;border-top:1px solid #E0E4E9;border-left:2px solid transparent;overflow:hidden;transition:all 100ms ease-out;-webkit-transition:all 100ms ease-out}@media (max-width: 783px){.wpr-menuItem{width:57px;height:50px;padding:0}}.wpr-menuItem:before{position:absolute;top:calc(50% - 12px);right:18px;text-align:center;font-size:1.4375rem;line-height:1;color:#121116;opacity:0.4;transition:all 150ms ease-out;-webkit-transition:all 150ms ease-out}.wpr-menuItem:hover,.wpr-menuItem.isActive{color:#121116;background:#fff;border-left:2px solid #F56640}.wpr-menuItem:hover .wpr-menuItem-title,.wpr-menuItem.isActive .wpr-menuItem-title{color:#F56640}.wpr-menuItem:hover:before,.wpr-menuItem.isActive:before{color:#F56640;opacity:1}.wpr-menuItem:focus{color:#121116}.wpr-menuItem:focus:before{color:#121116}.wpr-menuItem-title{font-size:.8125rem;line-height:1.46154;font-weight:bold;letter-spacing:-0.08px;text-transform:uppercase;color:#121116}@media (max-width: 783px){.wpr-menuItem-title{display:none !important}}.wpr-menuItem-description{margin-top:2px;color:#72777C;font-size:.8125rem;line-height:1.23077;transition:all 150ms ease-out;-webkit-transition:all 150ms ease-out}@media (max-width: 783px){.wpr-menuItem-description{display:none}}.wpr-menuItem.wpr-subMenuItem{display:none;padding:10px 20px 8px 25px}@media (max-width: 783px){.wpr-menuItem.wpr-subMenuItem{padding:8px 20px 8px 23px;height:35px}}.wpr-menuItem.wpr-subMenuItem .wpr-menuItem-title{display:inline-block;font-size:.8125rem;line-height:1.84615;text-transform:inherit;font-weight:600}.wpr-menuItem.wpr-subMenuItem:before{position:relative;display:inline-block;top:2px;right:2px;margin-right:8px;font-size:1rem;line-height:1}#wpr-nav-cache:before{right:20px}#wpr-nav-tools:before{right:20px}.wpr-sectionHeader{position:relative;border-bottom:1px solid #E0E4E9;padding-bottom:24px}.wpr-sectionHeader:before{content:'';position:absolute;display:block;width:48px;height:2px;bottom:-1px;left:0;background:#F56640}.wpr-sectionHeader .wpr-title1{line-height:48px}.wpr-sectionHeader .wpr-title1:before{display:inline-block;width:48px;height:48px;margin-right:24px;background:#FDE0D9;color:#F56640;text-align:center;border-radius:3px}.wpr-sectionHeader-title{margin-top:8px;padding-left:72px}.wpr-sectionHeader-description{color:#666;margin-top:8px;padding-left:72px}.wpr-sectionHeader-logo{vertical-align:top;margin-right:24px}.wpr-optionHeader{position:relative;display:flex;justify-content:space-between;margin-top:48px;padding-bottom:9px;border-bottom:1px solid #E0E4E9}.wpr-optionHeader .wpr-title2{line-height:24px;color:#F56640;padding-right:40px}.wpr-optionHeader .wpr-infoAction{margin-right:8px}.wpr-optionHeader.wpr-isHidden{display:none}.wpr-fieldsContainer{margin-top:8px}.wpr-fieldsContainer-description{color:#666}.wpr-fieldsContainer-description a:hover,.wpr-fieldsContainer-description a:focus{color:#1EADBF}.wpr-fieldsContainer-fieldset{margin-top:16px;background:#F9FAFB;padding:16px;border:1px solid #E8EBEE;border-radius:2px}.wpr-fieldsContainer-fieldset--split{display:flex}.wpr-fieldsContainer-fieldset--split .wpr-field+.wpr-field{border:none}.wpr-fieldsContainer-fieldset--split .wpr-field{flex:0 0 50%;padding:0}.wpr-fieldsContainer-fieldset--split .wpr-field:first-child{padding-right:15px}.wpr-fieldsContainer-fieldset--split .wpr-field:last-child{padding-left:15px}.wpr-fieldsContainer-helper{margin-top:16px;color:#D60E5B;font-weight:500}.wpr-fieldsContainer-helper:before{position:relative;top:3px;font-size:1.125rem;line-height:1;margin-right:4px}.wpr-fieldsContainer.wpr-isHidden{display:none}.wpr-infoAction{position:relative;height:24px;font-size:.8125rem;line-height:1.84615;vertical-align:middle;letter-spacing:-0.03em;font-weight:500;color:#666;white-space:nowrap;text-decoration:none;transition:all 200ms ease-out;-webkit-transition:all 200ms ease-out}.wpr-infoAction:before{position:absolute;margin-left:-26px;font-size:1.125rem;line-height:1.33333;transition:color 200ms ease-out;-webkit-transition:color 200ms ease-out}.wpr-infoAction--help{text-transform:uppercase;color:#02707F;font-weight:bold;font-size:.75rem;line-height:2;letter-spacing:0}@media (max-width: 783px){.wpr-infoAction--help{display:none}}.wpr-infoAction--help:before{color:#1EADBF}.wpr-infoAction:hover,.wpr-infoAction:focus{color:#F56640;outline:none}.wpr-infoAction:hover:before,.wpr-infoAction:focus:before{color:#FFA58B}.wpr-button{position:relative;display:inline-block;width:auto;padding:8px 24px;text-align:center;background:#F56640;box-shadow:0 4px 6px rgba(50,50,93,0.11),0 1px 3px rgba(0,0,0,0.08);text-transform:uppercase;text-decoration:none;letter-spacing:-0.08px;font-weight:bold;border-radius:4px;color:#fff !important;white-space:nowrap;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:all 200ms ease-out;-webkit-transition:all 200ms ease-out;font-size:.8125rem;line-height:1.53846}.wpr-button:hover,.wpr-button:focus{color:#fff !important;transform:translateY(-2px);box-shadow:0 7px 14px rgba(50,50,93,0.25),0 3px 6px rgba(0,0,0,0.2)}.wpr-button--small{padding:5px 0;letter-spacing:-0.08px;font-size:.6875rem;line-height:1.81818}.wpr-button--icon{min-width:160px;padding-left:8px;padding-right:40px;text-align:left}.wpr-button--icon:before{position:absolute;right:8px;font-size:.9375rem;line-height:1.33333}.wpr-button--fixed{position:fixed;display:flex;padding:8px 16px;right:24px;bottom:32px;border-radius:16px}.wpr-button--fixed:before{font-size:1.125rem;line-height:1;margin-right:8px}.wpr-button--purple{background:#2D1656}.wpr-button--blue{min-width:inherit;background:#1EADBF}.wpr-button--lightBlue{min-width:inherit;background:#40BACB}.wpr-button--red{background:#D33F49}.wpr-button--blueDark{background:#02707F}.wpr-button:focus{outline:none;color:#fff !important}.wpr-field{padding:16px 0;transition:opacity 150ms ease-out;-webkit-transition:opacity 150ms ease-out}.wpr-field+.wpr-field,.wpr-field+.wpr-warningContainer{border-top:1px solid #E0E4E9}.wpr-field:first-child{padding-top:0}.wpr-field:last-child{padding-bottom:0}.wpr-field-description{margin-top:4px;color:#666;font-size:.8125rem;line-height:1.53846}.wpr-field-description .wpr-js-popin{color:#444;text-decoration:underline}.wpr-field-description .wpr-js-popin:hover,.wpr-field-description .wpr-js-popin:focus{color:#1EADBF}.wpr-field-description a:hover,.wpr-field-description a:focus{color:#1EADBF}.wpr-field-description-helper{color:#00A66B}.wpr-field-description-label{font-size:.875rem;line-height:1.42857;font-weight:500;color:#666}.wpr-field-list{margin:0;color:#666;font-weight:500}.wpr-field-list li+li{margin-top:16px}.wpr-field-list li:before{position:relative;top:3px;margin-right:8px;color:#02707F;font-size:1.125rem;line-height:1.11111}.wpr-field-list a{text-decoration:none}.wpr-field-list a:hover,.wpr-field-list a:focus{color:#1EADBF}.wpr-field-betweenText{margin:0 16px;font-weight:bold}.wpr-field .wpr-button{margin:8px 0}.wpr-field .wpr-flex{display:flex;justify-content:space-between;align-items:center}@media (max-width: 783px){.wpr-field .wpr-flex{text-align:left;flex-direction:column}}.wpr-field .wpr-flex--egal>div{flex:0 0 50%}@media (max-width: 783px){.wpr-field .wpr-flex--egal>div{width:100%}}.wpr-field .wpr-flex--egal>div:last-child{text-align:right}@media (max-width: 783px){.wpr-field .wpr-flex--egal>div:last-child{text-align:left}}.wpr-field .wpr-flex--egal>div .wpr-field-description{font-style:normal;color:#666}.wpr-field p{margin-bottom:0}.wpr-field label{font-weight:500}.wpr-field h4{font-size:.875rem;line-height:1.71429}.wpr-field.wpr-isDisabled{opacity:0.55}.wpr-field.wpr-isParent{padding-bottom:0}.wpr-field.wpr-Delayjs{margin-top:16px}.wpr-field.wpr-isLastElem{margin-top:16px}.wpr-field.wpr-isHidden{display:none}.wpr-field .wpr-isHidden{display:none}.wpr-field--children{display:none;padding-left:32px}.wpr-field--children.wpr-isOpen{display:block;margin-top:16px}.wpr-field--children.wpr-field--textarea{padding-right:80px}@media (max-width: 1239px){.wpr-field--children.wpr-field--textarea{padding-right:32px}}@media (max-width: 783px){.wpr-field--children.wpr-field--textarea{padding-right:0}}.wpr-field--checkbox .wpr-field-description{margin-left:32px}.wpr-field--radio{padding:24px 16px}.wpr-field--radio:first-child{padding-top:8px}.wpr-field--radio:last-child{padding-bottom:8px}.wpr-field--radio .wpr-field-description{margin-left:88px}.wpr-field--radio .wpr-field-description button{color:#666}.wpr-field--split{display:inline-block;width:50%;padding-right:16px;padding-bottom:0}.wpr-field--split+.wpr-field--split{padding-left:16px;padding-right:0}.wpr-field--split+.wpr-field--split:nth-child(2){padding-top:0;border-top:none}.wpr-field--cache .wpr-field--number,.wpr-field--cache .wpr-field--select{display:inline-block;padding-top:0;width:auto;padding-bottom:0;font-weight:bold}.wpr-field--cache .wpr-field--select{position:relative;padding-left:8px;top:-2px;border-top:none}.wpr-field--cache .wpr-field--number .wpr-text input[type=number]{background:#F2F3F6;height:35px;border:1px solid #E0E4E9;font-family:inherit;font-size:1em}.wpr-field--cache .wpr-field-description{margin:8px 0;color:#00A66B}.wpr-field--cache .wpr-field-description-label{color:#121116}.wpr-fieldWarning{display:none;position:relative;padding:16px 16px 24px 56px;background:#19073B;margin:8px 0 0;color:#fff}.wpr-fieldWarning.wpr-isOpen{display:block}.wpr-fieldWarning:after{content:'';position:absolute;display:block;top:-8px;left:20px;width:0;height:0;border-style:solid;border-width:0 12px 8px 12px;border-color:transparent transparent #19073B transparent}.wpr-fieldWarning:before{content:'';position:absolute;display:block;width:calc(100% + 32px);height:100%;top:0;left:-16px;background:#19073B}.wpr-fieldWarning-title{position:relative;color:#F56640;font-size:.875rem;line-height:1.42857;font-weight:bold}.wpr-fieldWarning-title:before{position:absolute;left:-36px;font-size:1.5rem;line-height:.83333}.wpr-fieldWarning-description{position:relative;margin-top:8px}.wpr-fieldWarning .wpr-button{margin-top:16px}.wpr-warningContainer+.wpr-warningContainer,.wpr-warningContainer+.wpr-field,.wpr-field+.wpr-warningContainer{border-top:1px solid #E0E4E9;padding-top:16px}.wpr-documentation{padding:24px 16px;border-radius:4px;color:#fff;text-align:center;background:#40BACB}.wpr-documentation p{margin:8px 0 16px;font-weight:500}.wpr-documentation i{display:block;font-size:2.25rem;line-height:1;margin-bottom:8px}.wpr-documentation .wpr-button{padding-left:16px;padding-right:16px}.wpr-addon{padding:24px 0}.wpr-addon .wpr-flex{align-items:flex-start}@media (max-width: 783px){.wpr-addon .wpr-flex{align-items:center}}.wpr-addon .wpr-flex>div{text-align:left}.wpr-addon .wpr-addon-title{margin-bottom:16px;font-weight:500}.wpr-addon .wpr-field-description{font-style:normal}.wpr-addon .wpr-addon-logo{text-align:center;flex:0 0 160px}@media (max-width: 1239px){.wpr-addon .wpr-addon-logo{max-width:100px}.wpr-addon .wpr-addon-logo img{width:100%;height:auto}}@media (max-width: 1083px){.wpr-addon .wpr-addon-logo{max-width:160px}}@media (max-width: 783px){.wpr-addon .wpr-addon-logo{flex:0 0 auto;margin-bottom:16px}}.wpr-addon .wpr-addon-text{margin-left:32px;flex:1 1 auto}@media (max-width: 1239px){.wpr-addon .wpr-addon-text{margin-left:16px}}@media (max-width: 1083px){.wpr-addon .wpr-addon-text{margin-left:32px}}@media (max-width: 783px){.wpr-addon .wpr-addon-text{margin-left:0}}.wpr-addon .wpr-addon-text a{display:inline-block;margin-top:24px}.wpr-addon .wpr-addon-text .button{margin-top:24px}.wpr-notice{position:relative;color:#444;background:#EBFAF5 url("../img/bg-activated.svg") no-repeat 90% bottom;background-size:350px;margin-top:24px;border-radius:4px;overflow:hidden}.wpr-notice-container{padding:24px 25% 24px 40px}.wpr-notice-supTitle{font-size:1rem;line-height:1.375;font-weight:bold}.wpr-notice-title{font-size:1.5rem;line-height:1.33333;color:#3ECE9D;margin-top:16px;font-weight:bold}.wpr-notice-description{font-size:.875rem;line-height:1.57143;margin:16px 0 24px}.wpr-notice-continue{color:#666}.wpr-notice-close{position:absolute;top:24px;right:24px;color:#666;text-decoration:none;font-size:1.5rem;line-height:1;transition:color 200ms ease-out;-webkit-transition:color 200ms ease-out}.wpr-notice-close:hover{color:#444}.wpr-notice-close:focus{outline:none}.wpr-tools{position:relative;display:flex;flex-direction:row;padding:32px 0}@media (max-width: 1239px){.wpr-tools{flex-direction:column}}@media (max-width: 1083px){.wpr-tools{flex-direction:row}}@media (max-width: 783px){.wpr-tools{flex-direction:column}}.wpr-tools:nth-child(2){margin-top:16px}.wpr-tools+.wpr-tools{border-top:1px solid #E0E4E9}.wpr-tools-label{display:block}.wpr-tools-label:before{position:absolute;left:0;margin-top:5px;font-size:2.25rem;line-height:1;color:#F56640}.wpr-tools-col{flex:1 1 auto}.wpr-tools-col:first-child{padding-left:72px;padding-right:24px;min-width:340px}.wpr-tools-col:last-child{text-align:right}@media (max-width: 783px){.wpr-tools-col:last-child{text-align:left}}.wpr-tools .wpr-button{margin-top:24px;white-space:normal}.wpr-tools .wpr-field-description{font-style:normal;color:#666}.wpr-imagify{display:flex;justify-content:space-between;flex-wrap:wrap;margin-top:30px}.wpr-imagify-description{width:calc(100% / 3 * 1);padding-right:60px}@media (max-width: 1239px){.wpr-imagify-description{width:auto;padding-right:0}}.wpr-imagify-screenshot{width:calc(100% / 3 * 2)}@media (max-width: 1239px){.wpr-imagify-screenshot{margin-top:60px;width:auto}}.wpr-imagify-screenshot img{max-width:100%;height:auto}.wpr-imagify-more,.wpr-imagify-name{color:#00a8dc;font-weight:700;margin-bottom:0}.wpr-imagify-more::before{content:'\2713';color:#000;font-size:2rem;margin-right:5px}.wpr-imagify p{font-size:1rem}.wpr-imagify p:first-child{margin-top:0}.wpr-imagify ul{margin-top:0;margin-left:40px;list-style-type:'>'}.wpr-imagify li{padding-left:7px}.wpr-imagify a{text-decoration:none}.wpr-imagify a:hover{color:#00a8dc}.wpr-imagify .button-primary{background:#2abb9b;border:1px solid #bebebe;box-shadow:none;font-size:1rem;font-weight:700;height:auto;line-height:1;margin-top:60px;padding:20px 45px;text-shadow:none;text-transform:uppercase}.wpr-tutorials-section{display:flex;flex-flow:row wrap;justify-content:space-between}div.wpr-tutorial-item{width:calc( 96% / 3);margin-bottom:10px}.wpr-tutorial-link{cursor:pointer;transition:color 200ms ease-out}.wpr-tutorial-link:hover{color:#1EADBF}@media (max-width: 1083px){div.wpr-tutorial-item{width:calc( 96% / 2)}}@media (max-width: 783px){div.wpr-tutorial-item{width:100%}}.wpr-rocketcdn-modal{display:none}.wpr-rocketcdn-modal.is-open{display:block}.wpr-rocketcdn-modal__overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);display:flex;justify-content:center;align-items:center}.wpr-rocketcdn-modal__container{max-width:674px;max-height:100vh;overflow-y:auto;box-sizing:border-box}.wpr-rocketcdn-modal iframe{max-width:100%}.wpr-checkbox{position:relative;padding-left:32px}.wpr-checkbox label{user-select:none}.wpr-checkbox [type="checkbox"]:not(:checked),.wpr-checkbox [type="checkbox"]:checked{position:absolute;left:-9999px}.wpr-checkbox [type="checkbox"]:not(:checked)+label:before,.wpr-checkbox [type="checkbox"]:checked+label:before{content:'';position:absolute;left:0;top:4px;width:14px;height:14px;border:2px solid #444;border-radius:3px;transition:all 150ms ease-out;-webkit-transition:all 150ms ease-out}.wpr-checkbox [type="checkbox"]:not(:checked)+label:after,.wpr-checkbox [type="checkbox"]:checked+label:after{content:"\e921";position:absolute;top:5px;left:2px;color:#fff;font-family:'wpr-icomoon';speak:none;font-size:.875rem;line-height:1.28571;transition:all 150ms ease-out;-webkit-transition:all 150ms ease-out}.wpr-checkbox [type="checkbox"]:not(:checked)+label:after{opacity:0;transform:scale(2)}.wpr-checkbox [type="checkbox"]:checked+label:after{opacity:1;transform:scale(1)}.wpr-checkbox [type="checkbox"]:checked+label:before{background:#19073B;border-color:#19073B}.wpr-checkbox [type="checkbox"]:checked:focus+label:before{background:#665090;border:2px dotted #665090}.wpr-checkbox [type="checkbox"]:focus+label:before{border:2px dotted #444}.wpr-radio{position:relative;padding-left:88px}.wpr-radio label{user-select:none;font-weight:bold}.wpr-radio [type="checkbox"]:not(:checked),.wpr-radio [type="checkbox"]:checked{position:absolute;left:-9999px}.wpr-radio [type="checkbox"]:not(:checked)+label:before,.wpr-radio [type="checkbox"]:checked+label:before,.wpr-radio [type="checkbox"]:not(:checked)+label:after,.wpr-radio [type="checkbox"]:checked+label:after{content:'';position:absolute}.wpr-radio [type="checkbox"]:not(:checked)+label:before,.wpr-radio [type="checkbox"]:checked+label:before{left:0;width:52px;height:22px;background:#fff;border-radius:12px;border:1px solid #444;transition:all 150ms ease-out;-webkit-transition:all 150ms ease-out}.wpr-radio [type="checkbox"]:not(:checked)+label:after,.wpr-radio [type="checkbox"]:checked+label:after{width:18px;height:18px;border-radius:100%;background:#444;top:3px;left:3px;transition:all 150ms ease-out;-webkit-transition:all 150ms ease-out}.wpr-radio [type="checkbox"]:checked+label:before{border-color:#1EADBF}.wpr-radio [type="checkbox"]:checked+label:after{background:#1EADBF;left:33px}.wpr-radio [type="checkbox"]:checked+label .wpr-radio-ui,.wpr-radio [type="checkbox"]:not(:checked)+label .wpr-radio-ui:before,.wpr-radio [type="checkbox"]:checked+label .wpr-radio-ui:after{position:absolute;left:4px;width:52px;text-transform:uppercase;letter-spacing:-0.01em;font-weight:bold;font-size:.6875rem;line-height:2.18182;transition:all 150ms ease-out;-webkit-transition:all 150ms ease-out}.wpr-radio [type="checkbox"]:not(:checked)+label .wpr-radio-ui:before{content:attr(data-l10n-inactive);left:27px;color:#666}.wpr-radio [type="checkbox"]:checked+label .wpr-radio-ui:after{content:attr(data-l10n-active);color:#02707F}.wpr-radio--reverse{padding-right:72px;padding-left:0}.wpr-radio--reverse [type="checkbox"]:not(:checked)+label:before,.wpr-radio--reverse [type="checkbox"]:checked+label:before{right:0;left:inherit}.wpr-radio--reverse [type="checkbox"]:not(:checked)+label:after,.wpr-radio--reverse [type="checkbox"]:checked+label:after{right:33px;left:inherit}.wpr-radio--reverse [type="checkbox"]:checked+label:after{right:3px;left:inherit}.wpr-radio--reverse [type="checkbox"]:checked+label .wpr-radio-ui,.wpr-radio--reverse [type="checkbox"]:not(:checked)+label .wpr-radio-ui:before,.wpr-radio--reverse [type="checkbox"]:checked+label .wpr-radio-ui:after{right:-2px;left:inherit}.wpr-radio--reverse [type="checkbox"]:not(:checked)+label .wpr-radio-ui:before{right:-25px;left:inherit}.wpr-radio [type="checkbox"]:not(:checked):focus+label:before{border:1px dashed #444}.wpr-radio [type="checkbox"]:checked:focus+label:before{border:1px dashed #1EADBF}.wpr-radio--tips [type="checkbox"]:checked+label:before{border-color:#3ECE9D}.wpr-radio--tips [type="checkbox"]:checked+label:after{background:#3ECE9D}.wpr-radio--tips [type="checkbox"]:checked+label .wpr-radio-ui:after{color:#00A66B}.wpr-radio--tips [type="checkbox"]:checked:focus+label:before{border:1px dashed #3ECE9D}.wpr-select{position:relative}.wpr-select select{margin:0;padding:0 8px;height:36px;border:1px solid #E0E4E9;background:#F2F3F6;color:#121116;box-shadow:none;border-radius:0;letter-spacing:0.011em}.wpr-select select:focus{outline:none;border-color:#444;box-shadow:none}.wpr-select label{font-weight:bold;margin-left:8px}.wpr-textarea{margin-top:8px}.wpr-textarea textarea{padding:8px;width:100%;height:100px;font-family:Monaco;color:#121116;background:#fff;border:2px solid #c2cad4;border-radius:3px;font-size:.8125rem;line-height:1.23077;transition:border 200ms ease-out;-webkit-transition:border 200ms ease-out}.wpr-textarea textarea:focus{outline:none;border-color:#444;box-shadow:none}.wpr-textarea #delay_js_scripts{height:200px}.wpr-textarea+.wpr-field-description{color:#00A66B}.wpr-text label{color:#666}.wpr-text input[type=text],.wpr-text input[type=number]{margin-top:8px;padding:0 8px;width:100%;height:32px;color:#121116;background:#fff;border:2px solid #c2cad4;border-radius:3px;font-family:Monaco;font-size:.75rem;line-height:1.33333;transition:border 200ms ease-out;-webkit-transition:border 200ms ease-out}.wpr-text input[type=text]:focus,.wpr-text input[type=number]:focus{outline:none;border-color:#444;box-shadow:none}.wpr-text input[type=text].wpr-isError,.wpr-text input[type=number].wpr-isError{border-color:#D33F49}.wpr-text input[type=number]{width:80px}.wpr-text--number label{margin-right:8px}.wpr-upload input[type=file]{display:block;width:252px;margin:8px 8px 8px 0;padding:8px;border:1px solid #E0E4E9;background:#F2F3F6;color:#121116;font-size:.6875rem;line-height:1.45455}.wpr-upload input[type=file]:focus{outline:none;border-color:#444;box-shadow:none}.wpr-multiple{display:flex;align-items:center;flex-wrap:wrap}@media (max-width: 783px){.wpr-multiple{align-items:center;flex-direction:column}}.wpr-multiple .wpr-text{flex:1 1 auto;position:relative;top:-2px}@media (max-width: 783px){.wpr-multiple .wpr-text{width:100%}}.wpr-multiple .wpr-button{margin-left:16px;width:auto;min-width:inherit;padding-right:30px}@media (max-width: 783px){.wpr-multiple .wpr-button{margin-left:0}}.wpr-multiple input[type=text]{flex-grow:2}.wpr-multiple select{height:30px}.wpr-multiple-default{margin-right:20px}.wpr-multiple-list{display:none;padding:8px 0;margin:16px 0 0;background:#F2F3F6;border-radius:2px}.wpr-multiple-list li{margin-bottom:0;padding:4px 16px;font-size:.8125rem;line-height:1.23077;font-family:Monaco}.wpr-multiple-list li span{display:inline-block;transition:all 150ms ease-out;-webkit-transition:all 150ms ease-out}.wpr-multiple-close{position:relative;top:3px;font-size:1rem;line-height:1;transition:color 200ms ease-out;-webkit-transition:color 200ms ease-out}.wpr-multiple-close:focus{outline:none}.wpr-multiple-close:hover,.wpr-multiple-close:focus{color:#D33F49}.wpr-multiple-close:hover+span,.wpr-multiple-close:focus+span{color:#D33F49}
diff --git a/wp-content/plugins/wp-rocket/assets/css/wpr-modal.css b/wp-content/plugins/wp-rocket/assets/css/wpr-modal.css
new file mode 100644
index 00000000..d7ce1fe1
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/css/wpr-modal.css
@@ -0,0 +1 @@
+@font-face{font-family:'wpr-icomoon';src:url("../fonts/icomoon.eot?-4yq2oj");src:url("../fonts/icomoon.eot?#iefix-4yq2oj") format("embedded-opentype"),url("../fonts/icomoon.woff?-4yq2oj") format("woff"),url("../fonts/icomoon.ttf?-4yq2oj") format("truetype"),url("../fonts/icomoon.svg?-4yq2oj#icomoon") format("svg");font-weight:normal;font-style:normal}[class^="wpr-icon-"]:before,[class*=" wpr-icon-"]:after,[class^="wpr-icon-"]:after,[class*=" wpr-icon-"]:before,[id^="wpr-nav-"]:before,[id*=" wpr-nav-"]:after,[id^="wpr-nav-"]:after,[id*=" wpr-nav-"]:before{font-family:'wpr-icomoon';speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[class^="wpr-icon-"] span.hidden,[class*=" wpr-icon-"] span.hidden{display:inline-block;height:0;width:0;overflow:hidden}.wpr-icon-chevron-right:before{content:"\e900"}.wpr-icon-chevron-left:before{content:"\e900";transform:rotate(180deg)}.wpr-icon-chevron-down:before{content:"\e901";transform:scale(0.6)}.wpr-icon-chevron-up:before{content:"\e902";top:50%;transform:translateY(-50%) scale(0.6)}.wpr-icon-rollback:before{content:"\e903"}.wpr-icon-addon:before{content:"\e904"}.wpr-icon-addons:before{content:"\e905"}.wpr-icon-book:before{content:"\e906"}.wpr-icon-cdn:before{content:"\e907"}.wpr-icon-database:before{content:"\e908"}.wpr-icon-export:before{content:"\e909"}.wpr-icon-files:before{content:"\e90a"}.wpr-icon-help:before{content:"\e90b"}.wpr-icon-home:before{content:"\e90c"}.wpr-icon-import:before{content:"\e90d"}.wpr-icon-important:before{content:"\e90e"}.wpr-icon-information:before{content:"\e90f"}.wpr-icon-information2:before{content:"\e910"}.wpr-icon-interrogation:before{content:"\e911"}.wpr-icon-media:before{content:"\e912"}.wpr-icon-plus:before{content:"\e913"}.wpr-icon-refresh:before{content:"\e914"}.wpr-icon-rules:before{content:"\e915"}.wpr-icon-stack:before{content:"\e916"}.wpr-icon-tools:before{content:"\e917"}.wpr-icon-trash:before{content:"\e918"}.wpr-icon-user:before{content:"\e919"}.wpr-icon-check:before{content:"\e920"}.wpr-icon-check2:before{content:"\e921"}.wpr-icon-close:before{content:"\e922"}.wpr-Modal{display:none;position:fixed;width:550px;height:auto;top:50%;left:50%;background:#fff;color:#444;transform:translateX(-50%) translateY(-50%);z-index:100000;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-interpolation-mode:nearest-neighbor;image-rendering:optimizeQuality;text-rendering:optimizeLegibility}.wpr-Modal-overlay{display:none;position:fixed;width:100%;height:100%;top:0;left:0;background:rgba(0,0,0,0.7);z-index:99999}.wpr-Modal-header{display:flex;align-items:center;justify-content:space-between;padding:0 16px 0 32px;border-bottom:1px solid #EEE}.wpr-Modal-header>div{display:flex}.wpr-Modal-footer{display:flex;align-items:center;justify-content:space-between;height:64px;padding:0 32px;background:#F5F5F5}.wpr-Modal-close{font-size:0;color:#AAA;border:none;background:none;cursor:pointer}.wpr-Modal-close:hover{color:#008ec2}.wpr-Modal-close:before{font-size:1.25rem;line-height:1}.wpr-Modal-return{display:none;border:none;background:none;cursor:pointer;font-size:0;color:#777;position:relative;width:28px;left:-12px}.wpr-Modal-return.wpr-isOpen{display:inline-block}.wpr-Modal-return:before{font-size:14px;display:inline-block}.wpr-Modal-return:hover,.wpr-Modal-return:focus{outline:none;color:#008ec2}.wpr-Modal-cancel{color:#0073aa;text-decoration:underline;margin-left:8px;line-height:28px;border:none;background:none;cursor:pointer;font-weight:500}.wpr-Modal-cancel:hover{color:#008ec2}.wpr-Modal-question{display:none}.wpr-Modal-question.wpr-isOpen{display:block}.wpr-Modal-content{padding:8px 32px}.wpr-Modal-fieldHidden{display:none;padding-left:26px;margin-top:8px}.wpr-Modal-fieldHidden.wpr-isOpen{display:block}.wpr-Modal-fieldHidden input[type=text],.wpr-Modal-fieldHidden textarea{border:2px solid #A9ADB0;font-size:12px;border-radius:3px;transition:border 150ms ease-out;-webkit-transition:border 150ms ease-out}.wpr-Modal-fieldHidden input[type=text]:focus,.wpr-Modal-fieldHidden textarea:focus{outline:none;box-shadow:none;border-color:#777}.wpr-Modal-fieldHidden input[type=text]::-webkit-input-placeholder,.wpr-Modal-fieldHidden textarea::-webkit-input-placeholder{color:#ccc}.wpr-Modal-fieldHidden input[type=text] :-moz-placeholder,.wpr-Modal-fieldHidden textarea :-moz-placeholder{color:#ccc;opacity:1}.wpr-Modal-fieldHidden input[type=text]::-moz-placeholder,.wpr-Modal-fieldHidden textarea::-moz-placeholder{color:#ccc;opacity:1}.wpr-Modal-fieldHidden input[type=text] :-ms-input-placeholder,.wpr-Modal-fieldHidden textarea :-ms-input-placeholder{color:#ccc}.wpr-Modal-fieldHidden input[type=text]::-ms-input-placeholder,.wpr-Modal-fieldHidden textarea::-ms-input-placeholder{color:#ccc}.wpr-Modal-fieldHidden input[type=text]::placeholder,.wpr-Modal-fieldHidden textarea::placeholder{color:#ccc}.wpr-Modal-fieldHidden input[type=text]{width:300px;height:30px}.wpr-Modal-fieldHidden textarea{width:100%;height:60px;padding:5px}.wpr-Modal-hidden{display:none}.wpr-Modal-hidden.wpr-isOpen{display:block}.wpr-Modal-hidden button{cursor:pointer;border:none;margin-bottom:24px}.wpr-Modal-hidden .text-center{text-align:center}.wpr-Modal-hidden h3{display:none !important}.wpr-Modal-hidden a{color:#1B9AAA !important}.wpr-Modal-hidden a:hover{color:#0073aa !important}.wpr-Modal-safeMode{position:relative;padding:10px 12px 10px 52px;background:#EBFAF5;color:#00A66B;border-radius:2px;font-size:.8125rem;line-height:1.38462}.wpr-Modal-safeMode:before{position:absolute;display:block;width:24px;height:24px;left:12px;top:50%;margin-top:-14px;border-radius:50%;text-align:center;border:2px solid #00A66B;font-size:.875rem;line-height:1.71429}.wpr-Modal-safeMode-title{font-weight:bold;letter-spacing:0.011em;text-transform:uppercase}.wpr-Modal .button{font-weight:500;box-shadow:none;height:30px;border-bottom-width:3px}.wpr-Modal .button-primary.wpr-isDisabled{opacity:0.2;color:#fff !important;cursor:not-allowed;pointer-events:none}.wpr-Modal ul li{padding:4px 0}.wpr-Modal h2,.wpr-Modal h3{display:inline-block;font-size:1rem;line-height:1.1875}.wpr-Modal h2{max-width:430px;font-weight:600;color:#23282D;margin:12px 0}.wpr-Modal h3{font-weight:bold;color:#3D474E;margin:8px 0}.wpr-Modal input[type=radio]{margin-top:1px;margin-right:8px}.wpr-Modal .show-if-safe-mode{display:none}.wpr-button{position:relative;display:inline-block;width:auto;padding:8px 24px;text-align:center;background:#F56640;box-shadow:0 4px 6px rgba(50,50,93,0.11),0 1px 3px rgba(0,0,0,0.08);text-transform:uppercase;text-decoration:none;letter-spacing:-0.08px;font-weight:bold;border-radius:4px;color:#fff !important;white-space:nowrap;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:all 200ms ease-out;-webkit-transition:all 200ms ease-out;font-size:.8125rem;line-height:1.53846}.wpr-button:hover,.wpr-button:focus{color:#fff !important;transform:translateY(-2px);box-shadow:0 7px 14px rgba(50,50,93,0.25),0 3px 6px rgba(0,0,0,0.2)}.wpr-button--small{padding:5px 0;letter-spacing:-0.08px;font-size:.6875rem;line-height:1.81818}.wpr-button--icon{min-width:160px;padding-left:8px;padding-right:40px;text-align:left}.wpr-button--icon:before{position:absolute;right:8px;font-size:.9375rem;line-height:1.33333}.wpr-button--fixed{position:fixed;display:flex;padding:8px 16px;right:24px;bottom:32px;border-radius:16px}.wpr-button--fixed:before{font-size:1.125rem;line-height:1;margin-right:8px}.wpr-button--purple{background:#2D1656}.wpr-button--blue{min-width:inherit;background:#1EADBF}.wpr-button--lightBlue{min-width:inherit;background:#40BACB}.wpr-button--red{background:#D33F49}.wpr-button--blueDark{background:#02707F}.wpr-button:focus{outline:none;color:#fff !important}html[dir=rtl] .wpr-Modal-header{padding:0 32px 0 16px}html[dir=rtl] .wpr-Modal-return{left:12px;transform:rotate(180deg)}html[dir=rtl] .wpr-Modal-safeMode{padding:10px 52px 10px 12px}html[dir=rtl] .wpr-Modal-safeMode:before{left:inherit;right:12px}
diff --git a/wp-content/plugins/wp-rocket/assets/fonts/icomoon.eot b/wp-content/plugins/wp-rocket/assets/fonts/icomoon.eot
new file mode 100644
index 00000000..ca87b377
Binary files /dev/null and b/wp-content/plugins/wp-rocket/assets/fonts/icomoon.eot differ
diff --git a/wp-content/plugins/wp-rocket/assets/fonts/icomoon.svg b/wp-content/plugins/wp-rocket/assets/fonts/icomoon.svg
new file mode 100644
index 00000000..6a4cc83f
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/fonts/icomoon.svg
@@ -0,0 +1,39 @@
+
+
+
+Generated by IcoMoon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/fonts/icomoon.ttf b/wp-content/plugins/wp-rocket/assets/fonts/icomoon.ttf
new file mode 100644
index 00000000..6a0202c5
Binary files /dev/null and b/wp-content/plugins/wp-rocket/assets/fonts/icomoon.ttf differ
diff --git a/wp-content/plugins/wp-rocket/assets/fonts/icomoon.woff b/wp-content/plugins/wp-rocket/assets/fonts/icomoon.woff
new file mode 100644
index 00000000..5da2c2cb
Binary files /dev/null and b/wp-content/plugins/wp-rocket/assets/fonts/icomoon.woff differ
diff --git a/wp-content/plugins/wp-rocket/assets/img/automatic.svg b/wp-content/plugins/wp-rocket/assets/img/automatic.svg
new file mode 100644
index 00000000..35ee6fd4
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/automatic.svg
@@ -0,0 +1,16 @@
+
+
+
+ streamline-icon-fitness-biceps@24x24
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/bandwidth.svg b/wp-content/plugins/wp-rocket/assets/img/bandwidth.svg
new file mode 100644
index 00000000..fcb27549
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/bandwidth.svg
@@ -0,0 +1,17 @@
+
+
+
+ streamline-icon-space-rocket-earth@24x24
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/bg-activated.svg b/wp-content/plugins/wp-rocket/assets/img/bg-activated.svg
new file mode 100644
index 00000000..0d1abe29
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/bg-activated.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/configuration.svg b/wp-content/plugins/wp-rocket/assets/img/configuration.svg
new file mode 100644
index 00000000..57095a8b
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/configuration.svg
@@ -0,0 +1,20 @@
+
+
+
+ streamline-icon-show-hat-magician@24x24
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/heartbeat-hover.svg b/wp-content/plugins/wp-rocket/assets/img/heartbeat-hover.svg
new file mode 100644
index 00000000..bbb49d52
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/heartbeat-hover.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/heartbeat.svg b/wp-content/plugins/wp-rocket/assets/img/heartbeat.svg
new file mode 100644
index 00000000..6b580a59
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/heartbeat.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/icon-128x128.png b/wp-content/plugins/wp-rocket/assets/img/icon-128x128.png
new file mode 100644
index 00000000..0a863b92
Binary files /dev/null and b/wp-content/plugins/wp-rocket/assets/img/icon-128x128.png differ
diff --git a/wp-content/plugins/wp-rocket/assets/img/icon-256x256.png b/wp-content/plugins/wp-rocket/assets/img/icon-256x256.png
new file mode 100644
index 00000000..9b138672
Binary files /dev/null and b/wp-content/plugins/wp-rocket/assets/img/icon-256x256.png differ
diff --git a/wp-content/plugins/wp-rocket/assets/img/imagify-hover.svg b/wp-content/plugins/wp-rocket/assets/img/imagify-hover.svg
new file mode 100644
index 00000000..babcf00f
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/imagify-hover.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/imagify.svg b/wp-content/plugins/wp-rocket/assets/img/imagify.svg
new file mode 100644
index 00000000..eab8de01
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/imagify.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/infinite.svg b/wp-content/plugins/wp-rocket/assets/img/infinite.svg
new file mode 100644
index 00000000..eb78bc07
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/infinite.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-adblock.svg b/wp-content/plugins/wp-rocket/assets/img/logo-adblock.svg
new file mode 100644
index 00000000..6bf79a07
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/logo-adblock.svg
@@ -0,0 +1,31 @@
+
+
+
+ Group 15
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-cloudflare.svg b/wp-content/plugins/wp-rocket/assets/img/logo-cloudflare.svg
new file mode 100644
index 00000000..cbf0bd1e
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/logo-cloudflare.svg
@@ -0,0 +1,24 @@
+
+
+Logo cloudflare
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-cloudflare2.svg b/wp-content/plugins/wp-rocket/assets/img/logo-cloudflare2.svg
new file mode 100644
index 00000000..6c828652
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/logo-cloudflare2.svg
@@ -0,0 +1,24 @@
+
+
+Logo cloudflare
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-facebook.svg b/wp-content/plugins/wp-rocket/assets/img/logo-facebook.svg
new file mode 100644
index 00000000..c42dc76c
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/logo-facebook.svg
@@ -0,0 +1 @@
+
flogo_RGB_HEX-114
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-google-analytics.svg b/wp-content/plugins/wp-rocket/assets/img/logo-google-analytics.svg
new file mode 100644
index 00000000..95a4a155
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/logo-google-analytics.svg
@@ -0,0 +1,166 @@
+
+
+
+
image/svg+xml
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-sucuri.png b/wp-content/plugins/wp-rocket/assets/img/logo-sucuri.png
new file mode 100644
index 00000000..ed3a1991
Binary files /dev/null and b/wp-content/plugins/wp-rocket/assets/img/logo-sucuri.png differ
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-varnish.svg b/wp-content/plugins/wp-rocket/assets/img/logo-varnish.svg
new file mode 100644
index 00000000..a68c4d7e
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/logo-varnish.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-wprocket-dark.svg b/wp-content/plugins/wp-rocket/assets/img/logo-wprocket-dark.svg
new file mode 100644
index 00000000..0e5612cd
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/logo-wprocket-dark.svg
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/logo-wprocket-light.svg b/wp-content/plugins/wp-rocket/assets/img/logo-wprocket-light.svg
new file mode 100644
index 00000000..a96a01ac
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/logo-wprocket-light.svg
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/picto-wprocket-dark.svg b/wp-content/plugins/wp-rocket/assets/img/picto-wprocket-dark.svg
new file mode 100644
index 00000000..5e0ac459
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/picto-wprocket-dark.svg
@@ -0,0 +1,39 @@
+
+
+
+ Icons / WP Rocket / Light
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/picto-wprocket-light.svg b/wp-content/plugins/wp-rocket/assets/img/picto-wprocket-light.svg
new file mode 100644
index 00000000..f50d9e24
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/picto-wprocket-light.svg
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/play-alt.svg b/wp-content/plugins/wp-rocket/assets/img/play-alt.svg
new file mode 100644
index 00000000..b7d7b8bd
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/play-alt.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/play-hover.svg b/wp-content/plugins/wp-rocket/assets/img/play-hover.svg
new file mode 100644
index 00000000..7e8b00c7
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/play-hover.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/play.svg b/wp-content/plugins/wp-rocket/assets/img/play.svg
new file mode 100644
index 00000000..c2765297
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/play.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/img/plus.svg b/wp-content/plugins/wp-rocket/assets/img/plus.svg
new file mode 100644
index 00000000..2b78eaa7
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/plus.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/warning.svg b/wp-content/plugins/wp-rocket/assets/img/warning.svg
new file mode 100644
index 00000000..33f5e44e
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/img/warning.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wp-content/plugins/wp-rocket/assets/img/youtube.png b/wp-content/plugins/wp-rocket/assets/img/youtube.png
new file mode 100644
index 00000000..f5d4db11
Binary files /dev/null and b/wp-content/plugins/wp-rocket/assets/img/youtube.png differ
diff --git a/wp-content/plugins/wp-rocket/assets/js/browser-checker.js b/wp-content/plugins/wp-rocket/assets/js/browser-checker.js
new file mode 100644
index 00000000..cd6b8acb
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/browser-checker.js
@@ -0,0 +1,109 @@
+class RocketBrowserCompatibilityChecker {
+
+ constructor( options ) {
+ this.passiveSupported = false;
+
+ this._checkPassiveOption( this );
+ this.options = this.passiveSupported ? options : false;
+ }
+
+ /**
+ * Initializes browser check for addEventListener passive option.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
+ * @private
+ *
+ * @param self Instance of this object.
+ * @returns {boolean}
+ */
+ _checkPassiveOption( self ) {
+ try {
+ const options = {
+ // This function will be called when the browser attempts to access the passive property.
+ get passive() {
+ self.passiveSupported = true;
+ return false;
+ }
+ };
+
+ window.addEventListener( 'test', null, options );
+ window.removeEventListener( 'test', null, options );
+ } catch ( err ) {
+ self.passiveSupported = false;
+ }
+ }
+
+ /**
+ * Checks if the browser supports requestIdleCallback and cancelIdleCallback. If no, shims its behavior with a polyfills.
+ *
+ * @link @link https://developers.google.com/web/updates/2015/08/using-requestidlecallback
+ */
+ initRequestIdleCallback() {
+ if ( ! 'requestIdleCallback' in window ) {
+ window.requestIdleCallback = ( cb ) => {
+ const start = Date.now();
+ return setTimeout( () => {
+ cb( {
+ didTimeout: false,
+ timeRemaining: function timeRemaining() {
+ return Math.max( 0, 50 - ( Date.now() - start ) );
+ }
+ } );
+ }, 1 );
+ };
+ }
+
+ if ( ! 'cancelIdleCallback' in window ) {
+ window.cancelIdleCallback = ( id ) => clearTimeout( id );
+ }
+ }
+
+ /**
+ * Detects if data saver mode is on.
+ *
+ * @link https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/save-data/#detecting_the_save-data_setting
+ *
+ * @returns {boolean|boolean}
+ */
+ isDataSaverModeOn() {
+ return (
+ 'connection' in navigator
+ &&
+ true === navigator.connection.saveData
+ );
+ }
+
+ /**
+ * Checks if the browser supports link prefetch.
+ *
+ * @returns {boolean|boolean}
+ */
+ supportsLinkPrefetch() {
+ const elem = document.createElement( 'link' );
+ return (
+ elem.relList
+ &&
+ elem.relList.supports
+ &&
+ elem.relList.supports( 'prefetch' )
+ &&
+ window.IntersectionObserver
+ &&
+ 'isIntersecting' in IntersectionObserverEntry.prototype
+ );
+ }
+
+ isSlowConnection() {
+ return (
+ 'connection' in navigator
+ &&
+ 'effectiveType' in navigator.connection
+ &&
+ (
+ '2g' === navigator.connection.effectiveType
+ ||
+ 'slow-2g' === navigator.connection.effectiveType
+ )
+ )
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/assets/js/browser-checker.min.js b/wp-content/plugins/wp-rocket/assets/js/browser-checker.min.js
new file mode 100644
index 00000000..cee63e9e
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/browser-checker.min.js
@@ -0,0 +1 @@
+"use strict";var _createClass=function(){function defineProperties(target,props){for(var i=0;i
{
+ if ( document.querySelector( 'link[data-rocket-async="style"][rel="preload"]' ) ) {
+ setTimeout( wprRemoveCPCSS, 200 );
+ } else {
+ const elem = document.getElementById( 'rocket-critical-css' );
+ if ( elem && 'remove' in elem ) {
+ elem.remove();
+ }
+ }
+};
+
+if ( window.addEventListener ) {
+ window.addEventListener( 'load', wprRemoveCPCSS );
+} else if ( window.attachEvent ) {
+ window.attachEvent( 'onload', wprRemoveCPCSS );
+}
diff --git a/wp-content/plugins/wp-rocket/assets/js/cpcss-removal.min.js b/wp-content/plugins/wp-rocket/assets/js/cpcss-removal.min.js
new file mode 100644
index 00000000..599a4d70
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/cpcss-removal.min.js
@@ -0,0 +1 @@
+"use strict";var wprRemoveCPCSS=function wprRemoveCPCSS(){var elem;document.querySelector('link[data-rocket-async="style"][rel="preload"]')?setTimeout(wprRemoveCPCSS,200):(elem=document.getElementById("rocket-critical-css"))&&"remove"in elem&&elem.remove()};window.addEventListener?window.addEventListener("load",wprRemoveCPCSS):window.attachEvent&&window.attachEvent("onload",wprRemoveCPCSS);
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/js/editor/editor.js b/wp-content/plugins/wp-rocket/assets/js/editor/editor.js
new file mode 100644
index 00000000..a7eede39
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/editor/editor.js
@@ -0,0 +1 @@
+this.DisableEmbeds=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=2)}([function(e,t){!function(){e.exports=this.wp.blocks}()},function(e,t){!function(){e.exports=this.wp.domReady}()},function(e,t,n){"use strict";n.r(t);var r=n(0),o=n(1);n.n(o)()(function(){Object(r.unregisterBlockType)("core-embed/wordpress")})}]);
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload-scripts.js b/wp-content/plugins/wp-rocket/assets/js/lazyload-scripts.js
new file mode 100644
index 00000000..1145806d
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload-scripts.js
@@ -0,0 +1,108 @@
+class RocketLazyLoadScripts {
+
+ constructor( triggerEvents, browser ) {
+ this.attrName = 'data-rocketlazyloadscript';
+ this.browser = browser;
+ this.options = this.browser.options;
+ this.triggerEvents = triggerEvents;
+ this.userEventListener = this.triggerListener.bind( this );
+ }
+
+ /**
+ * Initializes the LazyLoad Scripts handler.
+ */
+ init() {
+ this._addEventListener( this );
+ }
+
+ /**
+ * Resets the handler.
+ */
+ reset() {
+ this._removeEventListener( this );
+ }
+
+ /**
+ * Adds a listener for each of the configured user interactivity event type. When an even is triggered, it invokes
+ * the triggerListener() method.
+ *
+ * @private
+ *
+ * @param self Instance of this object.
+ */
+ _addEventListener( self ) {
+ this.triggerEvents.forEach(
+ eventName => window.addEventListener( eventName, self.userEventListener, self.options )
+ );
+ }
+
+ /**
+ * Removes the listener for each of the configured user interactivity event type.
+ *
+ * @private
+ *
+ * @param self Instance of this object.
+ */
+ _removeEventListener( self ) {
+ this.triggerEvents.forEach(
+ eventName => window.removeEventListener( eventName, self.userEventListener, self.options )
+ );
+ }
+
+ /**
+ * Loads the script's src from the data attribute, which will then trigger the browser to request and
+ * load the script.
+ */
+ _loadScriptSrc() {
+ const scripts = document.querySelectorAll( `script[${ this.attrName }]` );
+
+ if ( 0 === scripts.length ) {
+ this.reset();
+ return;
+ }
+
+ Array.prototype.slice.call( scripts ).forEach( elem => {
+ const scriptSrc = elem.getAttribute( this.attrName );
+
+ elem.setAttribute( 'src', scriptSrc );
+ elem.removeAttribute( this.attrName );
+ } );
+
+ this.reset();
+ }
+
+ /**
+ * Window event listener - when triggered, invokes the load script src handler and then resets.
+ */
+ triggerListener() {
+ this._loadScriptSrc();
+ this._removeEventListener( this );
+ }
+
+ /**
+ * Named static constructor to encapsulate how to create the object.
+ */
+ static run() {
+ // Bail out if the browser checker does not exist.
+ if ( ! RocketBrowserCompatibilityChecker ) {
+ return;
+ }
+
+ const options = {
+ passive: true
+ };
+ const browser = new RocketBrowserCompatibilityChecker( options );
+ const instance = new RocketLazyLoadScripts(
+ [
+ 'keydown',
+ 'mouseover',
+ 'touchmove',
+ 'touchstart'
+ ],
+ browser
+ );
+ instance.init();
+ }
+}
+
+RocketLazyLoadScripts.run();
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload-scripts.min.js b/wp-content/plugins/wp-rocket/assets/js/lazyload-scripts.min.js
new file mode 100644
index 00000000..4612e759
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload-scripts.min.js
@@ -0,0 +1,3 @@
+(function() {
+"use strict";var e=function(){function n(e,t){for(var r=0;r -1) {
+ addOneShotEventListeners(element, instance);
+ addClass(element, settings.class_loading);
+ }
+
+ setSources(element, instance);
+ setWasProcessedData(element);
+ callbackIfSet(settings.callback_reveal, element);
+ callbackIfSet(settings.callback_set, element);
+ };
+
+ var isIntersecting = function isIntersecting(entry) {
+ return entry.isIntersecting || entry.intersectionRatio > 0;
+ };
+
+ var getObserverSettings = function getObserverSettings(settings) {
+ return {
+ root: settings.container === document ? null : settings.container,
+ rootMargin: settings.thresholds || settings.threshold + "px"
+ };
+ };
+
+ var setObserver = function setObserver(instance) {
+ if (!supportsIntersectionObserver) {
+ return false;
+ }
+
+ instance._observer = new IntersectionObserver(function (entries) {
+ entries.forEach(function (entry) {
+ return isIntersecting(entry) ? onEnter(entry.target, instance) : onExit(entry.target, instance);
+ });
+ }, getObserverSettings(instance._settings));
+ return true;
+ };
+
+ var LazyLoad = function LazyLoad(customSettings, elements) {
+ this._settings = getInstanceSettings(customSettings);
+ this._loadingCount = 0;
+ setObserver(this);
+ this.update(elements);
+ };
+
+ LazyLoad.prototype = {
+ update: function update(elements) {
+ var _this = this;
+
+ var settings = this._settings;
+
+ var _elements = elements || settings.container.querySelectorAll(settings.elements_selector);
+
+ this._elements = purgeProcessedElements(Array.prototype.slice.call(_elements) // NOTE: nodeset to array for IE compatibility
+ );
+
+ if (isBot || !this._observer) {
+ this.loadAll();
+ return;
+ }
+
+ this._elements.forEach(function (element) {
+ _this._observer.observe(element);
+ });
+ },
+ destroy: function destroy() {
+ var _this2 = this;
+
+ if (this._observer) {
+ this._elements.forEach(function (element) {
+ _this2._observer.unobserve(element);
+ });
+
+ this._observer = null;
+ }
+
+ this._elements = null;
+ this._settings = null;
+ },
+ load: function load(element, force) {
+ revealElement(element, this, force);
+ },
+ loadAll: function loadAll() {
+ var _this3 = this;
+
+ var elements = this._elements;
+ elements.forEach(function (element) {
+ revealAndUnobserve(element, _this3);
+ });
+ }
+ };
+ /* Automatic instances creation if required (useful for async script loading) */
+
+ if (runningOnBrowser) {
+ autoInitialize(LazyLoad, window.lazyLoadOptions);
+ }
+
+ return LazyLoad;
+});
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload/11.0.6/lazyload.min.js b/wp-content/plugins/wp-rocket/assets/js/lazyload/11.0.6/lazyload.min.js
new file mode 100644
index 00000000..c9ad5229
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload/11.0.6/lazyload.min.js
@@ -0,0 +1,2 @@
+function _extends(){return(_extends=Object.assign||function(t){for(var e=1;e-1&&(k(t,e),h(t,o.class_loading)),m(t,e),function(t){s(t,"was-processed","true")}(t),d(o.callback_reveal,t),d(o.callback_set,t))},O=function(t){return!!n&&(t._observer=new IntersectionObserver(function(e){e.forEach(function(e){return function(t){return t.isIntersecting||t.intersectionRatio>0}(e)?function(t,e){var n=e._settings;d(n.callback_enter,t),n.load_delay?A(t,e):L(t,e)}(e.target,t):function(t,e){var n=e._settings;d(n.callback_exit,t),n.load_delay&&x(t)}(e.target,t)})},{root:(e=t._settings).container===document?null:e.container,rootMargin:e.thresholds||e.threshold+"px"}),!0);var e},N=function(t,e){this._settings=function(t){return _extends({},r,t)}(t),this._loadingCount=0,O(this),this.update(e)};return N.prototype={update:function(t){var n=this,o=this._settings,r=t||o.container.querySelectorAll(o.elements_selector);this._elements=function(t){return t.filter(function(t){return!i(t)})}(Array.prototype.slice.call(r)),!e&&this._observer?this._elements.forEach(function(t){n._observer.observe(t)}):this.loadAll()},destroy:function(){var t=this;this._observer&&(this._elements.forEach(function(e){t._observer.unobserve(e)}),this._observer=null),this._elements=null,this._settings=null},load:function(t,e){z(t,this,e)},loadAll:function(){var t=this;this._elements.forEach(function(e){L(e,t)})}},t&&function(t,e){if(e)if(e.length)for(var n,o=0;n=e[o];o+=1)u(t,n);else u(t,e)}(N,window.lazyLoadOptions),N});
+//# sourceMappingURL=lazyload.min.js.map
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload/11.0.6/lazyload.min.js.map b/wp-content/plugins/wp-rocket/assets/js/lazyload/11.0.6/lazyload.min.js.map
new file mode 100644
index 00000000..327d95e0
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload/11.0.6/lazyload.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["lazyload.js"],"names":["global","factory","exports","_typeof","module","define","amd","LazyLoad","this","runningOnBrowser","window","isBot","test","defaultSettings","elements_selector","threshold","thresholds","document","createElement","data_srcset","data_sizes","data_bg","class_loading","class_loaded","class_error","load_delay","auto_unobserve","callback_enter","callback_exit","callback_reveal","callback_loaded","callback_error","callback_finish","getInstanceSettings","getData","element","attribute","value","getAttribute","setData","attrName","setAttribute","processedDataName","removeAttribute","getWasProcessedData","setTimeoutData","getTimeoutData","timeoutDataName","createInstance","elementToPurge","options","event","instance","classObj","createEvent","initCustomEvent","dispatchEvent","callbackIfSet","autoInitialize","callback","argument","updateLoadingCount","plusMinus","_elements","length","_loadingCount","_settings","getSourceTags","parentTag","childTag","sourceTags","i","children","setAttributeIfValue","tagName","settings","data_src","backgroundImage","IMG","parent","parentNode","forEach","sourceTag","setImageAttributes","IFRAME","VIDEO","load","setSources","elements","setSourcesFunctions","setSourcesFunction","setSourcesVideo","filter","srcDataValue","bgDataValue","setSourcesBgImage","addClass","className","addEventListener","eventName","handler","removeEventListener","genericLoadEventName","errorHandler","removeClass","loadHandler","addEventListeners","errorEventName","success","removeEventListeners","target","supportsClassList","remove","replace","RegExp","eventHandler","addOneShotEventListeners","revealAndUnobserve","observer","unobserve","cancelDelayLoad","timeoutId","clearTimeout","loadDelay","revealElement","force","managedTags","indexOf","setTimeout","callback_set","setObserver","_observer","IntersectionObserver","entries","entry","setWasProcessedData","delayLoad","onEnter","root","container","rootMargin","isIntersecting","customSettings","getObserverSettings","trueString","prototype","update","_this","supportsIntersectionObserver","querySelectorAll","purgeProcessedElements","Array","observe","loadAll","_this2","_this3","optionsItem"],"mappings":"+cAAC,SAAUA,EAAQC,GACC,YAAnB,oBAAOC,QAAP,YAAAC,QAAOD,WAA0C,oBAAXE,OAAyBA,OAAOF,QAAUD,IAC9D,mBAAXI,QAAyBA,OAAOC,IAAMD,OAAOJ,GACnDD,EAAOO,SAAWN,IAHnB,CAAAO,KAAA,WAAkBP,aAIlB,IAACQ,EAAmB,oBAAAC,OAIfC,EAFNF,KAAsB,aAAGC,SAGvBD,oBADIE,WAKN,gCAAkCC,KACjCH,UAAAA,WAKKI,EACLC,GAAiB,yBADMJ,OAGvBK,EACAC,GAJuB,cAAAC,SAAAC,cAAA,KAMvBC,EAAa,CACbC,kBAAY,MACZC,UAAOV,GARgBF,EAAAQ,SAAA,KASvBK,UAAAA,IACAC,WAAAA,KACAC,SAAAA,MACAC,YAAU,SACVC,WAAAA,QACAC,QAAAA,KACAC,cAAAA,UACAC,aAAAA,SACAC,YAAAA,QACAC,WAAAA,EACAC,gBAAAA,EAnBuBL,eAAxB,KAeCC,cAAe,KAOhBC,gBAAII,KACHH,gBAAO,KACPC,eAFD,KAHCC,gBAAiB,MAYZE,EAAU,SAACC,EAASC,GAMzB,OAAIC,EAAKC,aALMA,QAKKF,IAGnBG,EAAA,SAAAJ,EAAAC,EAAAC,GAJD,IAAIG,EAJWF,QAIaF,EAKpBK,OAARN,EAGkCA,EAAAM,aAC1BN,EAASO,GAHjBP,EAPDQ,gBAAAH,IAYMI,EAAsB,SAAAT,GAAO,MAZnB,SAehBD,EAAMW,EAnBN,kBAmBAA,EAAA,SAAAV,EAAAE,GAAA,OACCE,EAAQJ,EAvBe,aAuBWE,IAELS,EAAYX,SAAAA,GAASY,OAAAA,EAAAA,EAzB3B,eAgCOC,EAAW,SAAKC,EAAhBC,GAAA,IAAvBC,EAGRC,EAAA,IAAAC,EAAAH,GAKC,IAJDC,EAAMH,IAAAA,YALN,wBAKMA,CAAAA,OAA0BK,CAAAA,SAAAA,KAC/B,MAAIF,IAEJA,EAAIC,SAAWE,YAAA,gBAORC,gBAfR,yBAeqC,GAAO,EAAO,CAAEH,SAAAA,IALnD1C,OAAA8C,cAAAL,IAYF,IAAAM,EAASC,SAAAA,EAAeL,GACvBM,GACCA,EAAAC,IAGAC,EAAA,SAAAT,EAAAU,GACAd,EAAAA,eAAeK,EACT,IAHPD,EAGOW,UAAAC,QAAA,IAAAZ,EAAAa,eACNR,EAAAL,EAAAc,UAAAlC,kBAIAmC,EAAA,SAAAC,GAkBD,IAjBA,IAiBgBC,EAjBhBC,EAAA,GAiBSC,EAAI,EAAcF,EAAWD,EAAUI,SAASD,GAAKA,GAAK,EAf9C,WAAhBd,EAAAA,SACLa,EAAIX,KAAUU,GAGd,OAJDC,GAOClB,EAAA,SAAAjB,EAA0B2B,EAA1BzB,GAiBKA,GAdJF,EAAAM,aAAAD,EAAAH,IAGI8B,EAAgB,SAAAhC,EAAhBgC,GACLM,EAkBCtC,EAjBD,QACCD,EAAImC,EAASK,EAATtD,aAEHqD,EACDtC,EAmBA,SAlBDD,EAAAC,EAAOmC,EAAPnD,cAqBAsD,EAAoBtC,EAAS,MAAOD,EAAQC,EAASwC,EAASC,YAsC7DzC,EAAc0C,CACdC,IAvDA,SAAA3C,EAAAwC,GACA,IAAAI,EAAA5C,EAAA6C,WACD7C,GAAA,YAAQM,EAAAA,SAJT0B,EAAAY,GA0BaE,QAAQ,SAAAC,GAnBrBC,EAAMA,EAAqBR,KAY1BQ,EAZDhD,EAAAwC,IA2DCS,OA5CuBJ,SAAAA,EAAvBL,GAaAF,EAAoBtC,EAAS,MAAOD,EAAQC,EAASwC,EAASC,YAwB9DS,MAjCC,SAAmBlD,EAAAwC,GAClBQ,EAAmBD,GADpBD,QAAA,SAAAC,GAGAT,EAaCS,EAXFC,MAVDjD,EAAAgD,EAAAP,EAAAC,aAcCH,EAAAA,EAAoBtC,MAASD,EAAOA,EAAQC,EAASwC,WACrDxC,EAFDmD,SAiBAC,EAAA,SAAApD,EAAAiB,GAoBC,IAtIAoC,EAAAvC,EAsIM0B,EAAWvB,EAASc,UAPrBuB,EAAAA,EAAmBf,QACrBgB,EADwBD,EAAAf,GAE3BU,GAAAA,EAID,OAHCC,EAAOM,EAAAA,GAHoB9B,EAA5BT,EAAA,QAaEA,EAASW,WA5IVyB,EA4IsCpC,EAASW,UA5I/Cd,EA4I0Dd,EA7I3DqD,EAAAI,OAAA,SAAAzD,GAAA,OAAAA,IAAAc,OAuGkBkB,SAAAA,EAAchC,GAC/BmC,IAAAA,EAAApC,EAAmBC,EAAA+C,EAAaN,UAC/BH,EAAAA,EACCS,EACAP,EACAzC,SAGFuC,IACAtC,EAAAA,MAAA0C,gBAAA1C,QAAAA,OAAA0D,EAAA1D,OAGD2D,IACC3D,EAAM0D,MAAAA,gBAAuB1D,GAoB7B4D,CAAgB5D,EAAQuC,IAExBsB,EAAIN,SAAAA,EAAJO,GACCP,EACA7B,EAAAA,UAAAA,IAAkBoC,GAGlB9D,EAAA8D,YAAA9D,EAAA8D,UAAA,IAAA,IAAAA,GAgBAC,EAAA,SAAA/D,EAAAgE,EAAAC,GAYDjE,EAAQ+D,iBAAiBC,EAAWC,IAG/BC,EAAsB,SAAClE,EAASgE,EAAWC,GARjDjE,EAAMmE,oBAAuBH,EAA7BC,IASCjE,EAAQkE,SAAAA,EAAoBF,EAA5BI,GACAF,EAFDlE,EAnBMqE,OAmBNC,GAYCJ,EAAoBlE,EA9BG,aA8B0BsE,GARlDJ,EAAMK,EArBJ,QAqBIA,IAGLR,EAAiB/D,SAASwE,EAAAA,EAAgBJ,GAC1C,IAJD5B,EAAAvB,EAAAc,UAcO+B,EAAYW,EAAUjC,EAASpD,aAAeoD,EAASnD,YARxDqF,EAAAA,EACLR,EAAAA,gBACAA,EAAAA,eACAA,EAAAA,EAAmBS,QArDpB,SAAA3E,EAAA8D,GAsBKc,EATL5E,EAAM6D,UAAWgB,OAAXhB,GAGJ7D,EAAA8D,UAAA9D,EAAA8D,UACAgB,QAAA,IAAAC,OAAA,WAAAjB,EAAA,YAAA,KAWAgB,QAAQ,OAAQ,IAVjB9E,QAAAA,OAAQ8D,IA8CRO,CAAYrE,EAASwC,EAASrD,eAR/B0E,EAAMmB,EAAYlB,GACjBxC,EAAIkB,EAAWvB,GAEfS,EAAiB+C,GACdjC,IAKHqB,EAAA,SAAA7D,EAAAiB,GACAK,IAAAA,EAAcE,SAAdF,EAAcE,GAEdE,EAAAA,GAAkB,EAACT,GACnByD,EAbD1E,EAAAsE,EAAAF,IAeMa,EAAAA,SAAAA,EAAAA,GACLD,EAAMV,GAAc,EAAArD,GACnB+D,EAAahE,EAAaC,EAA1BmD,KA7BwB,SAACpE,EAASsE,EAAaF,GARjDL,EAAMA,EAfAM,OAeAN,GACL/D,EAAQ+D,EAfe,aAevBO,GACAP,EAFD/D,EAbE,QAaFoE,GAuCEG,CAHDvE,EAAAsE,EAAAF,IAKCY,EAAahE,CAAAA,MAAO,SAAOC,SAkBvBiE,EAAqB,SAAClF,EAASiB,GAPpC,IAAAkE,EAAK3C,EAASlD,UACb4F,EAAAA,EAAkBjE,GAClBkE,GAAAlE,EAAAc,UAAAxC,gBACA4F,EAAAC,UAAApF,IAqBIqF,EAAkB,SAAArF,GATxB,IAAAsF,EAAe3E,EAACX,GACfsF,IAEAC,aAAK/C,GACJ9B,EAAAV,EAAA,QAEDqF,EAAAA,SAAerF,EAAfiB,GACA,IAPDuE,EAAAvE,EAAAc,UAAAzC,WAoBKgG,EAAY3E,EAAeX,GAXhCsF,IAECA,EAAKA,WAAW,WACfJ,EAAQlF,EAAAiB,GACRoE,EAAArF,IAcEwF,GAbHD,EAAYvF,EAACsF,KAiBRG,EAAgB,SAACzF,EAASiB,EAAUyE,GAb1C,IAAAlD,EAAevB,EAAGc,WACjB2D,GAAIF,EAAqBzD,KAEzB4D,EAAAC,QAAe5F,EAAAuC,UAAA,IACd0C,EAAQjF,EAAAiB,GACR4C,EAAA7D,EAAAwC,EAAArD,gBACDmG,EAAAA,EAAYO,GA7Qe,SAAA7F,GAAOI,EAAAJ,EAhBnC,gBAIgB,QA0RdkF,CAAAA,GACAG,EAAAA,EAAgBrF,gBAAhBA,GACAsB,EAAEkE,EAHHM,aAAA9F,KAYA+F,EAAgBH,SAAAA,GACfX,QAAAA,IAqBDhE,EAAS+E,UAAY,IAAIC,qBAAqB,SAAAC,GAlB9C9C,EAAAA,QAAWpD,SAAAA,GAAD,OAMY,SAAAmG,GAAK,OAf5BA,EAAMV,gBAAgBU,EAAhBV,kBAAiBzF,EAUtBoG,CAAAA,GApEA,SAAApG,EAAAiB,GAUA,IAAMuB,EAAWvB,EAASc,UAN1BwC,EAAAA,EAAkBvE,eAASsE,GAT5B9B,EAAAlD,WAcA+G,EAAMC,EAAUrF,GAIdiE,EAAmBlF,EAASiB,GAwD7BK,CAAAA,EAAckB,OAAS9C,GA1CT,SAACM,EAASiB,GARzB,IAAMiE,EAAAA,EAAqBnD,UAC1BT,EAAI6D,EAAWlE,cAAfjB,GACAyF,EAAAA,YAECN,EAASC,GA+CV9D,CAAAA,EAAckB,OAASsD,MAMiB,CAhBxCS,MAgB2B/D,EAeJvB,EAASc,WA/B5ByE,YAAU/F,SAAoBT,KAAUwC,EAAAgE,UAC3CC,WAD2CjE,EACnC3D,YAAA2D,EAAA5D,UAAA,QAYJ8H,GAGsB,IAAAlE,GAmBtBpE,EAAW,SAASuI,EAAgBtD,GAnB1ChF,KAAMuI,UAzTkB,SAAAD,GACxB,OAAME,SAAa,GAAnBnI,EAAAiI,GAwTMC,CAAAA,GAA8BvI,KAAAyD,cAAK,EACxCyE,EAAM/D,MACNiE,KAAAA,OAAAA,IAkDE,OA1BHrI,EAAS0I,UAAY,CArBrBC,OAAMhB,SAAW1C,GAAG,IAAA2D,EAAA3I,KACfmE,EAACyE,KAAAA,UACJrF,EACAyB,GAuBCb,EAASgE,UAAUU,iBAAiB1E,EAAS7D,mBArB9CuH,KAAAA,UAxS6B,SAAA7C,GAA/B,OAAM8D,EAAAA,OAAAA,SAAAA,GAAyB,OAAA1G,EAAzB0G,KAwSIrE,CAAasE,MAAAN,UACpBJ,MAAAA,KAAeP,KAKjB3H,GAAAH,KAAA2H,UAKA3H,KAAAuD,UAAKE,QAAgB,SAAA9B,GACrB+F,EAAAA,UAAYsB,QAAZrH,KAjBD3B,KAAAiJ,WAqBAlJ,QAAQ,WAAR,IAAAmJ,EAAAlJ,KACC0I,KAAMf,YAAqB3H,KAAAuD,UAAAkB,QAAA,SAAA9C,GAuBxBuH,EAAKvB,UAAUZ,UAAUpF,KAE1B3B,KAAK2H,UAAY,MAElB3H,KAAKuD,UAAY,KArBjBvD,KAAA0D,UAAKH,MAILuB,KAAA,SAASnD,EAAUgG,GAClBP,EAAK6B,EAALjJ,KAAAqH,IAwBF4B,QAAS,WAAW,IAAAE,EAAAnJ,KApBduD,KAAUkB,UACdA,QAAKkD,SAAAA,GACLd,EAFDlF,EAAAwH,OAOClJ,GAhUoC,SAArC4C,EAAAH,GACA,GAACA,EAGDC,GAAAA,EAAMI,OAEP7C,IAAAA,IAAO8C,EAAAA,EAAAA,EAAPoG,EAAA1G,EAAAqB,GAAAA,GAAA,EAZDvB,EAAAK,EAAAuG,QAUmD5G,EAAjDK,EAAAH,GA6TEQ,CAAInD,EAAJG,OAAe6G,iBAEhBhH","file":"lazyload.min.js","sourcesContent":["(function (global, factory) {\n\ttypeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n\ttypeof define === 'function' && define.amd ? define(factory) :\n\t(global.LazyLoad = factory());\n}(this, (function () { 'use strict';\n\nconst runningOnBrowser = typeof window !== \"undefined\";\n\nconst isBot =\n\t(runningOnBrowser && !(\"onscroll\" in window)) ||\n\t(typeof navigator !== \"undefined\" &&\n\t\t/(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent));\n\nconst supportsIntersectionObserver =\n\trunningOnBrowser && \"IntersectionObserver\" in window;\n\nconst supportsClassList =\n\trunningOnBrowser && \"classList\" in document.createElement(\"p\");\n\nconst defaultSettings = {\n\telements_selector: \"img\",\n\tcontainer: isBot || runningOnBrowser ? document : null,\n\tthreshold: 300,\n\tthresholds: null,\n\tdata_src: \"src\",\n\tdata_srcset: \"srcset\",\n\tdata_sizes: \"sizes\",\n\tdata_bg: \"bg\",\n\tclass_loading: \"loading\",\n\tclass_loaded: \"loaded\",\n\tclass_error: \"error\",\n\tload_delay: 0,\n\tauto_unobserve: true,\n\tcallback_enter: null,\n\tcallback_exit: null,\n\tcallback_reveal: null,\n\tcallback_loaded: null,\n\tcallback_error: null,\n\tcallback_finish: null\n};\n\nvar getInstanceSettings = customSettings => {\n\treturn Object.assign({}, defaultSettings, customSettings);\n};\n\nconst dataPrefix = \"data-\";\nconst processedDataName = \"was-processed\";\nconst timeoutDataName = \"ll-timeout\";\nconst trueString = \"true\";\n\nconst getData = (element, attribute) => {\n\treturn element.getAttribute(dataPrefix + attribute);\n};\n\nconst setData = (element, attribute, value) => {\n\tvar attrName = dataPrefix + attribute;\n\tif (value === null) {\n\t\telement.removeAttribute(attrName);\n\t\treturn;\n\t}\n\telement.setAttribute(attrName, value);\n};\n\nconst setWasProcessedData = element =>\n\tsetData(element, processedDataName, trueString);\n\nconst getWasProcessedData = element =>\n\tgetData(element, processedDataName) === trueString;\n\nconst setTimeoutData = (element, value) =>\n\tsetData(element, timeoutDataName, value);\n\nconst getTimeoutData = element => getData(element, timeoutDataName);\n\nconst purgeProcessedElements = elements => {\n\treturn elements.filter(element => !getWasProcessedData(element));\n};\n\nconst purgeOneElement = (elements, elementToPurge) => {\n\treturn elements.filter(element => element !== elementToPurge);\n};\n\n/* Creates instance and notifies it through the window element */\nconst createInstance = function(classObj, options) {\n\tvar event;\n\tlet eventString = \"LazyLoad::Initialized\";\n\tlet instance = new classObj(options);\n\ttry {\n\t\t// Works in modern browsers\n\t\tevent = new CustomEvent(eventString, { detail: { instance } });\n\t} catch (err) {\n\t\t// Works in Internet Explorer (all versions)\n\t\tevent = document.createEvent(\"CustomEvent\");\n\t\tevent.initCustomEvent(eventString, false, false, { instance });\n\t}\n\twindow.dispatchEvent(event);\n};\n\n/* Auto initialization of one or more instances of lazyload, depending on the \n options passed in (plain object or an array) */\nfunction autoInitialize(classObj, options) {\n\tif (!options) {\n\t\treturn;\n\t}\n\tif (!options.length) {\n\t\t// Plain object\n\t\tcreateInstance(classObj, options);\n\t} else {\n\t\t// Array of objects\n\t\tfor (let i = 0, optionsItem; (optionsItem = options[i]); i += 1) {\n\t\t\tcreateInstance(classObj, optionsItem);\n\t\t}\n\t}\n}\n\nconst callbackIfSet = (callback, argument) => {\n\tif (callback) {\n\t\tcallback(argument);\n\t}\n};\n\nconst updateLoadingCount = (instance, plusMinus) => {\n\tinstance._loadingCount += plusMinus;\n\tif (instance._elements.length === 0 && instance._loadingCount === 0) {\n\t\tcallbackIfSet(instance._settings.callback_finish);\n\t}\n};\n\nconst getSourceTags = parentTag => {\n\tlet sourceTags = [];\n\tfor (let i = 0, childTag; (childTag = parentTag.children[i]); i += 1) {\n\t\tif (childTag.tagName === \"SOURCE\") {\n\t\t\tsourceTags.push(childTag);\n\t\t}\n\t}\n\treturn sourceTags;\n};\n\nconst setAttributeIfValue = (element, attrName, value) => {\n\tif (!value) {\n\t\treturn;\n\t}\n\telement.setAttribute(attrName, value);\n};\n\nconst setImageAttributes = (element, settings) => {\n\tsetAttributeIfValue(\n\t\telement,\n\t\t\"sizes\",\n\t\tgetData(element, settings.data_sizes)\n\t);\n\tsetAttributeIfValue(\n\t\telement,\n\t\t\"srcset\",\n\t\tgetData(element, settings.data_srcset)\n\t);\n\tsetAttributeIfValue(element, \"src\", getData(element, settings.data_src));\n};\n\nconst setSourcesImg = (element, settings) => {\n\tconst parent = element.parentNode;\n\n\tif (parent && parent.tagName === \"PICTURE\") {\n\t\tlet sourceTags = getSourceTags(parent);\n\t\tsourceTags.forEach(sourceTag => {\n\t\t\tsetImageAttributes(sourceTag, settings);\n\t\t});\n\t}\n\n\tsetImageAttributes(element, settings);\n};\n\nconst setSourcesIframe = (element, settings) => {\n\tsetAttributeIfValue(element, \"src\", getData(element, settings.data_src));\n};\n\nconst setSourcesVideo = (element, settings) => {\n\tlet sourceTags = getSourceTags(element);\n\tsourceTags.forEach(sourceTag => {\n\t\tsetAttributeIfValue(\n\t\t\tsourceTag,\n\t\t\t\"src\",\n\t\t\tgetData(sourceTag, settings.data_src)\n\t\t);\n\t});\n\tsetAttributeIfValue(element, \"src\", getData(element, settings.data_src));\n\telement.load();\n};\n\nconst setSourcesBgImage = (element, settings) => {\n\tconst srcDataValue = getData(element, settings.data_src);\n\tconst bgDataValue = getData(element, settings.data_bg);\n\n\tif (srcDataValue) {\n\t\telement.style.backgroundImage = `url(\"${srcDataValue}\")`;\n\t}\n\n\tif (bgDataValue) {\n\t\telement.style.backgroundImage = bgDataValue;\n\t}\n};\n\nconst setSourcesFunctions = {\n\tIMG: setSourcesImg,\n\tIFRAME: setSourcesIframe,\n\tVIDEO: setSourcesVideo\n};\n\nconst setSources = (element, instance) => {\n\tconst settings = instance._settings;\n\tconst tagName = element.tagName;\n\tconst setSourcesFunction = setSourcesFunctions[tagName];\n\tif (setSourcesFunction) {\n\t\tsetSourcesFunction(element, settings);\n\t\tupdateLoadingCount(instance, 1);\n\t\tinstance._elements = purgeOneElement(instance._elements, element);\n\t\treturn;\n\t}\n\tsetSourcesBgImage(element, settings);\n};\n\nconst addClass = (element, className) => {\n\tif (supportsClassList) {\n\t\telement.classList.add(className);\n\t\treturn;\n\t}\n\telement.className += (element.className ? \" \" : \"\") + className;\n};\n\nconst removeClass = (element, className) => {\n\tif (supportsClassList) {\n\t\telement.classList.remove(className);\n\t\treturn;\n\t}\n\telement.className = element.className.\n\t\treplace(new RegExp(\"(^|\\\\s+)\" + className + \"(\\\\s+|$)\"), \" \").\n\t\treplace(/^\\s+/, \"\").\n\t\treplace(/\\s+$/, \"\");\n};\n\nconst genericLoadEventName = \"load\";\nconst mediaLoadEventName = \"loadeddata\";\nconst errorEventName = \"error\";\n\nconst addEventListener = (element, eventName, handler) => {\n\telement.addEventListener(eventName, handler);\n};\n\nconst removeEventListener = (element, eventName, handler) => {\n\telement.removeEventListener(eventName, handler);\n};\n\nconst addEventListeners = (element, loadHandler, errorHandler) => {\n\taddEventListener(element, genericLoadEventName, loadHandler);\n\taddEventListener(element, mediaLoadEventName, loadHandler);\n\taddEventListener(element, errorEventName, errorHandler);\n};\n\nconst removeEventListeners = (element, loadHandler, errorHandler) => {\n\tremoveEventListener(element, genericLoadEventName, loadHandler);\n\tremoveEventListener(element, mediaLoadEventName, loadHandler);\n\tremoveEventListener(element, errorEventName, errorHandler);\n};\n\nconst eventHandler = function(event, success, instance) {\n\tvar settings = instance._settings;\n\tconst className = success ? settings.class_loaded : settings.class_error;\n\tconst callback = success\n\t\t? settings.callback_loaded\n\t\t: settings.callback_error;\n\tconst element = event.target;\n\n\tremoveClass(element, settings.class_loading);\n\taddClass(element, className);\n\tcallbackIfSet(callback, element);\n\n\tupdateLoadingCount(instance, -1);\n};\n\nconst addOneShotEventListeners = (element, instance) => {\n\tconst loadHandler = event => {\n\t\teventHandler(event, true, instance);\n\t\tremoveEventListeners(element, loadHandler, errorHandler);\n\t};\n\tconst errorHandler = event => {\n\t\teventHandler(event, false, instance);\n\t\tremoveEventListeners(element, loadHandler, errorHandler);\n\t};\n\taddEventListeners(element, loadHandler, errorHandler);\n};\n\nconst managedTags = [\"IMG\", \"IFRAME\", \"VIDEO\"];\n\nconst onEnter = (element, instance) => {\n\tconst settings = instance._settings;\n\tcallbackIfSet(settings.callback_enter, element);\n\tif (!settings.load_delay) {\n\t\trevealAndUnobserve(element, instance);\n\t\treturn;\n\t}\n\tdelayLoad(element, instance);\n};\n\nconst revealAndUnobserve = (element, instance) => {\n\tvar observer = instance._observer;\n\trevealElement(element, instance);\n\tif (observer && instance._settings.auto_unobserve) {\n\t\tobserver.unobserve(element);\n\t}\n};\n\nconst onExit = (element, instance) => {\n\tconst settings = instance._settings;\n\tcallbackIfSet(settings.callback_exit, element);\n\tif (!settings.load_delay) {\n\t\treturn;\n\t}\n\tcancelDelayLoad(element);\n};\n\nconst cancelDelayLoad = element => {\n\tvar timeoutId = getTimeoutData(element);\n\tif (!timeoutId) {\n\t\treturn; // do nothing if timeout doesn't exist\n\t}\n\tclearTimeout(timeoutId);\n\tsetTimeoutData(element, null);\n};\n\nconst delayLoad = (element, instance) => {\n\tvar loadDelay = instance._settings.load_delay;\n\tvar timeoutId = getTimeoutData(element);\n\tif (timeoutId) {\n\t\treturn; // do nothing if timeout already set\n\t}\n\ttimeoutId = setTimeout(function() {\n\t\trevealAndUnobserve(element, instance);\n\t\tcancelDelayLoad(element);\n\t}, loadDelay);\n\tsetTimeoutData(element, timeoutId);\n};\n\nconst revealElement = (element, instance, force) => {\n\tvar settings = instance._settings;\n\tif (!force && getWasProcessedData(element)) {\n\t\treturn; // element has already been processed and force wasn't true\n\t}\n\tif (managedTags.indexOf(element.tagName) > -1) {\n\t\taddOneShotEventListeners(element, instance);\n\t\taddClass(element, settings.class_loading);\n\t}\n\tsetSources(element, instance);\n\tsetWasProcessedData(element);\n\tcallbackIfSet(settings.callback_reveal, element);\n\tcallbackIfSet(settings.callback_set, element);\n};\n\nconst isIntersecting = entry =>\n\tentry.isIntersecting || entry.intersectionRatio > 0;\n\nconst getObserverSettings = settings => ({\n\troot: settings.container === document ? null : settings.container,\n\trootMargin: settings.thresholds || settings.threshold + \"px\"\n});\n\nconst setObserver = instance => {\n\tif (!supportsIntersectionObserver) {\n\t\treturn false;\n\t}\n\tinstance._observer = new IntersectionObserver(entries => {\n\t\tentries.forEach(entry =>\n\t\t\tisIntersecting(entry)\n\t\t\t\t? onEnter(entry.target, instance)\n\t\t\t\t: onExit(entry.target, instance)\n\t\t);\n\t}, getObserverSettings(instance._settings));\n\treturn true;\n};\n\nconst LazyLoad = function(customSettings, elements) {\n\tthis._settings = getInstanceSettings(customSettings);\n\tthis._loadingCount = 0;\n\tsetObserver(this);\n\tthis.update(elements);\n};\n\nLazyLoad.prototype = {\n\tupdate: function(elements) {\n\t\tconst settings = this._settings;\n\t\tconst _elements =\n\t\t\telements ||\n\t\t\tsettings.container.querySelectorAll(settings.elements_selector);\n\n\t\tthis._elements = purgeProcessedElements(\n\t\t\tArray.prototype.slice.call(_elements) // NOTE: nodeset to array for IE compatibility\n\t\t);\n\n\t\tif (isBot || !this._observer) {\n\t\t\tthis.loadAll();\n\t\t\treturn;\n\t\t}\n\n\t\tthis._elements.forEach(element => {\n\t\t\tthis._observer.observe(element);\n\t\t});\n\t},\n\n\tdestroy: function() {\n\t\tif (this._observer) {\n\t\t\tthis._elements.forEach(element => {\n\t\t\t\tthis._observer.unobserve(element);\n\t\t\t});\n\t\t\tthis._observer = null;\n\t\t}\n\t\tthis._elements = null;\n\t\tthis._settings = null;\n\t},\n\n\tload: function(element, force) {\n\t\trevealElement(element, this, force);\n\t},\n\n\tloadAll: function() {\n\t\tvar elements = this._elements;\n\t\telements.forEach(element => {\n\t\t\trevealAndUnobserve(element, this);\n\t\t});\n\t}\n};\n\n/* Automatic instances creation if required (useful for async script loading) */\nif (runningOnBrowser) {\n\tautoInitialize(LazyLoad, window.lazyLoadOptions);\n}\n\nreturn LazyLoad;\n\n})));\n"]}
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.js b/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.js
new file mode 100644
index 00000000..c00114ef
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.js
@@ -0,0 +1,491 @@
+function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
+
+function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
+
+(function (global, factory) {
+ (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.LazyLoad = factory();
+})(this, function () {
+ 'use strict';
+
+ var runningOnBrowser = typeof window !== "undefined";
+ var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent);
+ var supportsIntersectionObserver = runningOnBrowser && "IntersectionObserver" in window;
+ var supportsClassList = runningOnBrowser && "classList" in document.createElement("p");
+ var defaultSettings = {
+ elements_selector: "img",
+ container: isBot || runningOnBrowser ? document : null,
+ threshold: 300,
+ thresholds: null,
+ data_src: "src",
+ data_srcset: "srcset",
+ data_sizes: "sizes",
+ data_bg: "bg",
+ class_loading: "loading",
+ class_loaded: "loaded",
+ class_error: "error",
+ load_delay: 0,
+ auto_unobserve: true,
+ callback_enter: null,
+ callback_exit: null,
+ callback_reveal: null,
+ callback_loaded: null,
+ callback_error: null,
+ callback_finish: null,
+ use_native: false
+ };
+
+ var getInstanceSettings = function getInstanceSettings(customSettings) {
+ return _extends({}, defaultSettings, customSettings);
+ };
+ /* Creates instance and notifies it through the window element */
+
+
+ var createInstance = function createInstance(classObj, options) {
+ var event;
+ var eventString = "LazyLoad::Initialized";
+ var instance = new classObj(options);
+
+ try {
+ // Works in modern browsers
+ event = new CustomEvent(eventString, {
+ detail: {
+ instance: instance
+ }
+ });
+ } catch (err) {
+ // Works in Internet Explorer (all versions)
+ event = document.createEvent("CustomEvent");
+ event.initCustomEvent(eventString, false, false, {
+ instance: instance
+ });
+ }
+
+ window.dispatchEvent(event);
+ };
+ /* Auto initialization of one or more instances of lazyload, depending on the
+ options passed in (plain object or an array) */
+
+
+ function autoInitialize(classObj, options) {
+ if (!options) {
+ return;
+ }
+
+ if (!options.length) {
+ // Plain object
+ createInstance(classObj, options);
+ } else {
+ // Array of objects
+ for (var i = 0, optionsItem; optionsItem = options[i]; i += 1) {
+ createInstance(classObj, optionsItem);
+ }
+ }
+ }
+
+ var dataPrefix = "data-";
+ var processedDataName = "was-processed";
+ var timeoutDataName = "ll-timeout";
+ var trueString = "true";
+
+ var getData = function getData(element, attribute) {
+ return element.getAttribute(dataPrefix + attribute);
+ };
+
+ var setData = function setData(element, attribute, value) {
+ var attrName = dataPrefix + attribute;
+
+ if (value === null) {
+ element.removeAttribute(attrName);
+ return;
+ }
+
+ element.setAttribute(attrName, value);
+ };
+
+ var setWasProcessedData = function setWasProcessedData(element) {
+ return setData(element, processedDataName, trueString);
+ };
+
+ var getWasProcessedData = function getWasProcessedData(element) {
+ return getData(element, processedDataName) === trueString;
+ };
+
+ var setTimeoutData = function setTimeoutData(element, value) {
+ return setData(element, timeoutDataName, value);
+ };
+
+ var getTimeoutData = function getTimeoutData(element) {
+ return getData(element, timeoutDataName);
+ };
+
+ var purgeProcessedElements = function purgeProcessedElements(elements) {
+ return elements.filter(function (element) {
+ return !getWasProcessedData(element);
+ });
+ };
+
+ var purgeOneElement = function purgeOneElement(elements, elementToPurge) {
+ return elements.filter(function (element) {
+ return element !== elementToPurge;
+ });
+ };
+
+ var callbackIfSet = function callbackIfSet(callback, argument) {
+ if (callback) {
+ callback(argument);
+ }
+ };
+
+ var updateLoadingCount = function updateLoadingCount(instance, plusMinus) {
+ instance._loadingCount += plusMinus;
+
+ if (instance._elements.length === 0 && instance._loadingCount === 0) {
+ callbackIfSet(instance._settings.callback_finish);
+ }
+ };
+
+ var getSourceTags = function getSourceTags(parentTag) {
+ var sourceTags = [];
+
+ for (var i = 0, childTag; childTag = parentTag.children[i]; i += 1) {
+ if (childTag.tagName === "SOURCE") {
+ sourceTags.push(childTag);
+ }
+ }
+
+ return sourceTags;
+ };
+
+ var setAttributeIfValue = function setAttributeIfValue(element, attrName, value) {
+ if (!value) {
+ return;
+ }
+
+ element.setAttribute(attrName, value);
+ };
+
+ var setImageAttributes = function setImageAttributes(element, settings) {
+ setAttributeIfValue(element, "sizes", getData(element, settings.data_sizes));
+ setAttributeIfValue(element, "srcset", getData(element, settings.data_srcset));
+ setAttributeIfValue(element, "src", getData(element, settings.data_src));
+ };
+
+ var setSourcesImg = function setSourcesImg(element, settings) {
+ var parent = element.parentNode;
+
+ if (parent && parent.tagName === "PICTURE") {
+ var sourceTags = getSourceTags(parent);
+ sourceTags.forEach(function (sourceTag) {
+ setImageAttributes(sourceTag, settings);
+ });
+ }
+
+ setImageAttributes(element, settings);
+ };
+
+ var setSourcesIframe = function setSourcesIframe(element, settings) {
+ setAttributeIfValue(element, "src", getData(element, settings.data_src));
+ };
+
+ var setSourcesVideo = function setSourcesVideo(element, settings) {
+ var sourceTags = getSourceTags(element);
+ sourceTags.forEach(function (sourceTag) {
+ setAttributeIfValue(sourceTag, "src", getData(sourceTag, settings.data_src));
+ });
+ setAttributeIfValue(element, "src", getData(element, settings.data_src));
+ element.load();
+ };
+
+ var setSourcesBgImage = function setSourcesBgImage(element, settings) {
+ var srcDataValue = getData(element, settings.data_src);
+ var bgDataValue = getData(element, settings.data_bg);
+
+ if (srcDataValue) {
+ element.style.backgroundImage = "url(\"".concat(srcDataValue, "\")");
+ }
+
+ if (bgDataValue) {
+ element.style.backgroundImage = bgDataValue;
+ }
+ };
+
+ var setSourcesFunctions = {
+ IMG: setSourcesImg,
+ IFRAME: setSourcesIframe,
+ VIDEO: setSourcesVideo
+ };
+
+ var setSources = function setSources(element, instance) {
+ var settings = instance._settings;
+ var tagName = element.tagName;
+ var setSourcesFunction = setSourcesFunctions[tagName];
+
+ if (setSourcesFunction) {
+ setSourcesFunction(element, settings);
+ updateLoadingCount(instance, 1);
+ instance._elements = purgeOneElement(instance._elements, element);
+ return;
+ }
+
+ setSourcesBgImage(element, settings);
+ };
+
+ var addClass = function addClass(element, className) {
+ if (supportsClassList) {
+ element.classList.add(className);
+ return;
+ }
+
+ element.className += (element.className ? " " : "") + className;
+ };
+
+ var removeClass = function removeClass(element, className) {
+ if (supportsClassList) {
+ element.classList.remove(className);
+ return;
+ }
+
+ element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ").replace(/^\s+/, "").replace(/\s+$/, "");
+ };
+
+ var genericLoadEventName = "load";
+ var mediaLoadEventName = "loadeddata";
+ var errorEventName = "error";
+
+ var addEventListener = function addEventListener(element, eventName, handler) {
+ element.addEventListener(eventName, handler);
+ };
+
+ var removeEventListener = function removeEventListener(element, eventName, handler) {
+ element.removeEventListener(eventName, handler);
+ };
+
+ var addEventListeners = function addEventListeners(element, loadHandler, errorHandler) {
+ addEventListener(element, genericLoadEventName, loadHandler);
+ addEventListener(element, mediaLoadEventName, loadHandler);
+ addEventListener(element, errorEventName, errorHandler);
+ };
+
+ var removeEventListeners = function removeEventListeners(element, loadHandler, errorHandler) {
+ removeEventListener(element, genericLoadEventName, loadHandler);
+ removeEventListener(element, mediaLoadEventName, loadHandler);
+ removeEventListener(element, errorEventName, errorHandler);
+ };
+
+ var eventHandler = function eventHandler(event, success, instance) {
+ var settings = instance._settings;
+ var className = success ? settings.class_loaded : settings.class_error;
+ var callback = success ? settings.callback_loaded : settings.callback_error;
+ var element = event.target;
+ removeClass(element, settings.class_loading);
+ addClass(element, className);
+ callbackIfSet(callback, element);
+ updateLoadingCount(instance, -1);
+ };
+
+ var addOneShotEventListeners = function addOneShotEventListeners(element, instance) {
+ var loadHandler = function loadHandler(event) {
+ eventHandler(event, true, instance);
+ removeEventListeners(element, loadHandler, errorHandler);
+ };
+
+ var errorHandler = function errorHandler(event) {
+ eventHandler(event, false, instance);
+ removeEventListeners(element, loadHandler, errorHandler);
+ };
+
+ addEventListeners(element, loadHandler, errorHandler);
+ };
+
+ var managedTags = ["IMG", "IFRAME", "VIDEO"];
+
+ var onEnter = function onEnter(element, instance) {
+ var settings = instance._settings;
+ callbackIfSet(settings.callback_enter, element);
+
+ if (!settings.load_delay) {
+ revealAndUnobserve(element, instance);
+ return;
+ }
+
+ delayLoad(element, instance);
+ };
+
+ var revealAndUnobserve = function revealAndUnobserve(element, instance) {
+ var observer = instance._observer;
+ revealElement(element, instance);
+
+ if (observer && instance._settings.auto_unobserve) {
+ observer.unobserve(element);
+ }
+ };
+
+ var onExit = function onExit(element, instance) {
+ var settings = instance._settings;
+ callbackIfSet(settings.callback_exit, element);
+
+ if (!settings.load_delay) {
+ return;
+ }
+
+ cancelDelayLoad(element);
+ };
+
+ var cancelDelayLoad = function cancelDelayLoad(element) {
+ var timeoutId = getTimeoutData(element);
+
+ if (!timeoutId) {
+ return; // do nothing if timeout doesn't exist
+ }
+
+ clearTimeout(timeoutId);
+ setTimeoutData(element, null);
+ };
+
+ var delayLoad = function delayLoad(element, instance) {
+ var loadDelay = instance._settings.load_delay;
+ var timeoutId = getTimeoutData(element);
+
+ if (timeoutId) {
+ return; // do nothing if timeout already set
+ }
+
+ timeoutId = setTimeout(function () {
+ revealAndUnobserve(element, instance);
+ cancelDelayLoad(element);
+ }, loadDelay);
+ setTimeoutData(element, timeoutId);
+ };
+
+ var revealElement = function revealElement(element, instance, force) {
+ var settings = instance._settings;
+
+ if (!force && getWasProcessedData(element)) {
+ return; // element has already been processed and force wasn't true
+ }
+
+ if (managedTags.indexOf(element.tagName) > -1) {
+ addOneShotEventListeners(element, instance);
+ addClass(element, settings.class_loading);
+ }
+
+ setSources(element, instance);
+ setWasProcessedData(element);
+ callbackIfSet(settings.callback_reveal, element);
+ callbackIfSet(settings.callback_set, element);
+ };
+
+ var isIntersecting = function isIntersecting(entry) {
+ return entry.isIntersecting || entry.intersectionRatio > 0;
+ };
+
+ var getObserverSettings = function getObserverSettings(settings) {
+ return {
+ root: settings.container === document ? null : settings.container,
+ rootMargin: settings.thresholds || settings.threshold + "px"
+ };
+ };
+
+ var setObserver = function setObserver(instance) {
+ if (!supportsIntersectionObserver) {
+ return false;
+ }
+
+ instance._observer = new IntersectionObserver(function (entries) {
+ entries.forEach(function (entry) {
+ return isIntersecting(entry) ? onEnter(entry.target, instance) : onExit(entry.target, instance);
+ });
+ }, getObserverSettings(instance._settings));
+ return true;
+ };
+
+ var nativeLazyTags = ["IMG", "IFRAME"];
+
+ var shouldUseNative = function shouldUseNative(settings) {
+ return settings.use_native && "loading" in HTMLImageElement.prototype;
+ };
+
+ var loadAllNative = function loadAllNative(instance) {
+ instance._elements.forEach(function (element) {
+ if (nativeLazyTags.indexOf(element.tagName) === -1) {
+ return;
+ }
+
+ element.setAttribute("loading", "lazy");
+ revealElement(element, instance);
+ });
+ };
+
+ var nodeSetToArray = function nodeSetToArray(nodeSet) {
+ return Array.prototype.slice.call(nodeSet);
+ };
+
+ var queryElements = function queryElements(settings) {
+ return settings.container.querySelectorAll(settings.elements_selector);
+ };
+
+ var getElements = function getElements(elements, settings) {
+ return purgeProcessedElements(nodeSetToArray(elements || queryElements(settings)));
+ };
+
+ var LazyLoad = function LazyLoad(customSettings, elements) {
+ this._settings = getInstanceSettings(customSettings);
+ this._loadingCount = 0;
+ setObserver(this);
+ this.update(elements);
+ };
+
+ LazyLoad.prototype = {
+ update: function update(elements) {
+ var _this = this;
+
+ var settings = this._settings;
+ this._elements = getElements(elements, settings);
+
+ if (isBot || !this._observer) {
+ this.loadAll();
+ return;
+ }
+
+ if (shouldUseNative(settings)) {
+ loadAllNative(this);
+ this._elements = getElements(elements, settings);
+ }
+
+ this._elements.forEach(function (element) {
+ _this._observer.observe(element);
+ });
+ },
+ destroy: function destroy() {
+ var _this2 = this;
+
+ if (this._observer) {
+ this._elements.forEach(function (element) {
+ _this2._observer.unobserve(element);
+ });
+
+ this._observer = null;
+ }
+
+ this._elements = null;
+ this._settings = null;
+ },
+ load: function load(element, force) {
+ revealElement(element, this, force);
+ },
+ loadAll: function loadAll() {
+ var _this3 = this;
+
+ this._elements.forEach(function (element) {
+ revealAndUnobserve(element, _this3);
+ });
+ }
+ };
+ /* Automatic instances creation if required (useful for async script loading) */
+
+ if (runningOnBrowser) {
+ autoInitialize(LazyLoad, window.lazyLoadOptions);
+ }
+
+ return LazyLoad;
+});
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.min.js b/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.min.js
new file mode 100644
index 00000000..12facf20
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.min.js
@@ -0,0 +1,2 @@
+function _extends(){return(_extends=Object.assign||function(t){for(var e=1;e-1&&(I(t,e),h(t,o.class_loading)),b(t,e),function(t){s(t,"was-processed","true")}(t),d(o.callback_reveal,t),d(o.callback_set,t))},O=function(t){return!!n&&(t._observer=new IntersectionObserver(function(e){e.forEach(function(e){return function(t){return t.isIntersecting||t.intersectionRatio>0}(e)?function(t,e){var n=e._settings;d(n.callback_enter,t),n.load_delay?x(t,e):A(t,e)}(e.target,t):function(t,e){var n=e._settings;d(n.callback_exit,t),n.load_delay&&L(t)}(e.target,t)})},{root:(e=t._settings).container===document?null:e.container,rootMargin:e.thresholds||e.threshold+"px"}),!0);var e},N=["IMG","IFRAME"],C=function(t,e){return function(t){return t.filter(function(t){return!c(t)})}((n=t||function(t){return t.container.querySelectorAll(t.elements_selector)}(e),Array.prototype.slice.call(n)));var n},M=function(t,e){this._settings=function(t){return _extends({},r,t)}(t),this._loadingCount=0,O(this),this.update(e)};return M.prototype={update:function(t){var n,o=this,r=this._settings;(this._elements=C(t,r),!e&&this._observer)?(function(t){return t.use_native&&"loading"in HTMLImageElement.prototype}(r)&&((n=this)._elements.forEach(function(t){-1!==N.indexOf(t.tagName)&&(t.setAttribute("loading","lazy"),z(t,n))}),this._elements=C(t,r)),this._elements.forEach(function(t){o._observer.observe(t)})):this.loadAll()},destroy:function(){var t=this;this._observer&&(this._elements.forEach(function(e){t._observer.unobserve(e)}),this._observer=null),this._elements=null,this._settings=null},load:function(t,e){z(t,this,e)},loadAll:function(){var t=this;this._elements.forEach(function(e){A(e,t)})}},t&&function(t,e){if(e)if(e.length)for(var n,o=0;n=e[o];o+=1)a(t,n);else a(t,e)}(M,window.lazyLoadOptions),M});
+//# sourceMappingURL=lazyload.min.js.map
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.min.js.map b/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.min.js.map
new file mode 100644
index 00000000..ed990c2c
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload/12.0/lazyload.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["lazyload.js"],"names":["global","factory","exports","_typeof","module","define","amd","LazyLoad","this","runningOnBrowser","window","isBot","test","defaultSettings","elements_selector","threshold","thresholds","document","createElement","data_srcset","data_sizes","data_bg","class_loading","class_loaded","class_error","load_delay","auto_unobserve","callback_enter","callback_exit","callback_reveal","callback_loaded","callback_error","callback_finish","use_native","getInstanceSettings","createInstance","classObj","options","event","detail","instance","CustomEvent","err","createEvent","eventString","dispatchEvent","dataPrefix","element","attribute","processedDataName","setData","value","attrName","getData","removeAttribute","getWasProcessedData","setTimeoutData","setWasProcessedData","getTimeoutData","callback","argument","updateLoadingCount","_loadingCount","plusMinus","_elements","length","callbackIfSet","_settings","getSourceTags","parentTag","childTag","sourceTags","i","children","setAttribute","setAttributeIfValue","tagName","settings","data_src","backgroundImage","IMG","parent","parentNode","forEach","sourceTag","setImageAttributes","IFRAME","VIDEO","load","setSources","elements","setSourcesFunctions","setSourcesFunction","setSourcesVideo","timeoutDataName","srcDataValue","bgDataValue","setSourcesBgImage","addClass","className","addEventListener","eventName","handler","removeEventListener","genericLoadEventName","errorHandler","removeClass","loadHandler","addEventListeners","errorEventName","success","removeEventListeners","target","supportsClassList","remove","replace","RegExp","eventHandler","addOneShotEventListeners","revealAndUnobserve","observer","unobserve","cancelDelayLoad","timeoutId","clearTimeout","loadDelay","revealElement","force","managedTags","indexOf","setTimeout","callback_set","setObserver","_observer","IntersectionObserver","entries","entry","delayLoad","onEnter","root","container","rootMargin","isIntersecting","nativeLazyTags","getElements","trueString","purgeProcessedElements","getObserverSettings","querySelectorAll","queryElements","shouldUseNative","customSettings","loadAllNative","prototype","update","_this","nodeSetToArray","loadAll","destroy","_this2","_this3","autoInitialize","optionsItem"],"mappings":"+cAAC,SAAUA,EAAQC,GACC,YAAnB,oBAAOC,QAAP,YAAAC,QAAOD,WAA0C,oBAAXE,OAAyBA,OAAOF,QAAUD,IAC9D,mBAAXI,QAAyBA,OAAOC,IAAMD,OAAOJ,GACnDD,EAAOO,SAAWN,IAHnB,CAAAO,KAAA,WAAkBP,aAIlB,IAACQ,EAAmB,oBAAAC,OAIfC,EAFNF,KAAsB,aAAGC,SAGvBD,oBADIE,WAKN,gCAAkCC,KACjCH,UAAAA,WAKKI,EACLC,GAAiB,yBADMJ,OAGvBK,EACAC,GAJuB,cAAAC,SAAAC,cAAA,KAMvBC,EAAa,CACbC,kBAAY,MACZC,UAAOV,GARgBF,EAAAQ,SAAA,KASvBK,UAAAA,IACAC,WAAAA,KACAC,SAAAA,MACAC,YAAU,SACVC,WAAAA,QACAC,QAAAA,KACAC,cAAAA,UACAC,aAAAA,SACAC,YAAAA,QACAC,WAAAA,EACAC,gBAAAA,EACAC,eAAY,KApBWL,cAAxB,KAgBCC,gBAAiB,KAOlBC,gBAAII,KACHH,eAAO,KACPC,gBAFD,KAIAC,YAAA,GAKCE,EAAI,SAAAC,EAAAC,GACH,IAAAC,EACuCC,EAAQ,IAAAH,EAAAC,GAAEG,IAAZF,EAArC,IAAAG,YAAYA,wBAAZ,CAAAF,OAAA,CAAAC,SAAAA,KACA,MAACE,IAEDJ,EAAAA,SAAQrB,YAAS0B,gBACjBL,gBAJYG,yBAIUG,GAAa,EAAO,CAA1CJ,SAAAA,IAAiD9B,OAAjDmC,cAAAP,IAiBCH,IAKGW,EAAU,SAAAC,EAAhBC,GACA,OAAMC,EAAAA,aANHd,QAMHa,IAQME,EAAU,SAACH,EAASC,EAAWG,GAJrC,IAAAC,EAVGjB,QAUGkB,EACEN,OAAPI,EAIAJ,EAAIK,aAAWN,EAAaE,GAH5BD,EAFDO,gBAAAF,IAgBMG,EAAsB,SAAAR,GAAO,MAvBlC,SAiBAA,EAAAA,EAnBE,kBA4BGS,EAAiB,SAACT,EAASI,GAAV,OANvBD,EAAMO,EArBJ,aAqB0BN,IAA5BO,EAAA,SAAAX,GAAA,OAAAM,EAAAN,EArBE,eA8BIW,EAAc,SAAGC,EAAjBD,GAAwBC,GAAAA,EAA9BC,IAG+BC,EAAKN,SAAAA,EAAoBR,GAAzBP,EAA9BsB,eAAAC,EADD,IAAAvB,EAAAwB,UAAAC,QAAA,IAAAzB,EAAAsB,eAiBEI,EAAc1B,EAAS2B,UAAUnC,kBAZlCoC,EAAA,SAAAC,GAkBA,IAjBA,IAiBgBC,EAnBjBC,EAAA,GAmBUC,EAAI,EAAcF,EAAWD,EAAUI,SAASD,GAAKA,GAAK,EAf9C,WAAhBN,EAAAA,SACLK,EAAIZ,KAAUW,GAGd,OAJDC,GAOC/B,EAAA,SAAAO,EAA0BgB,EAA1BZ,GAiBKA,GAdJJ,EAAA2B,aAAAtB,EAAAD,IAGIiB,EAAgB,SAAArB,EAAhBqB,GACLO,EAkBC5B,EAjBD,QACCM,EAAIiB,EAASM,EAATxD,aAEHuD,EACD5B,EAmBA,SAlBDM,EAAAN,EAAOwB,EAAPpD,cAqBAwD,EAAoB5B,EAAS,MAAOM,EAAQN,EAAS8B,EAASC,YAsC7D/B,EAAcgC,CACdC,IAvDA,SAAAjC,EAAA8B,GACA,IAAAI,EAAAlC,EAAAmC,WACDnC,GAAA,YAAQ2B,EAAAA,SAJTN,EAAAa,GA0BaE,QAAQ,SAAAC,GAnBrBC,EAAMA,EAAqBR,KAY1BQ,EAZDtC,EAAA8B,IA2DCS,OA5CuBJ,SAAAA,EAAvBL,GAaAF,EAAoB5B,EAAS,MAAOM,EAAQN,EAAS8B,EAASC,YAwB9DS,MAjCC,SAAmBxC,EAAA8B,GAClBQ,EAAmBD,GADpBD,QAAA,SAAAC,GAGAT,EAaCS,EAXFC,MAVDhC,EAAA+B,EAAAP,EAAAC,aAcCH,EAAAA,EAAoB5B,MAASM,EAAOA,EAAQN,EAAS8B,WACrD9B,EAFDyC,SAiBAC,EAAA,SAAA1C,EAAAP,GAoBC,IA3GsBkD,EAAjBlC,EA2GCqB,EAAWrC,EAAS2B,UAPrBwB,EAAAA,EAAmBf,QACrBgB,EADwBD,EAAAf,GAE3BU,GAAAA,EAID,OAHCC,EAAOM,EAAAA,GAHoBhC,EAA5BrB,EAAA,QAaEA,EAASwB,WAjHY0B,EAiHgBlD,EAASwB,UAjH1CR,EAiHqDT,EAjHpC2C,EACtBxC,OAAQH,SAAAA,GAAS+C,OAAAA,IAAiB3C,OA0EjBiB,SAAAA,EAAcrB,GAC/BwB,IAAAA,EAAAlB,EAAmBN,EAAAqC,EAAaN,UAC/BH,EAAAA,EACCS,EACAP,EACAxB,SAGFsB,IACA5B,EAAAA,MAAAgC,gBAAAhC,QAAAA,OAAAgD,EAAAhD,OAGDiD,IACCjD,EAAMgD,MAAAA,gBAAuBhD,GAoB7BkD,CAAgBlD,EAAQ6B,IAExBsB,EAAIN,SAAAA,EAAJO,GACCP,EACA/B,EAAAA,UAAAA,IAAkBsC,GAGlBpD,EAAAoD,YAAApD,EAAAoD,UAAA,IAAA,IAAAA,GAgBAC,EAAA,SAAArD,EAAAsD,EAAAC,GAYDvD,EAAQqD,iBAAiBC,EAAWC,IAG/BC,EAAsB,SAACxD,EAASsD,EAAWC,GARjDvD,EAAMyD,oBAAuBH,EAA7BC,IASCvD,EAAQwD,SAAAA,EAAoBF,EAA5BI,GACAF,EAFDxD,EAnBM2D,OAmBNC,GAYCJ,EAAoBxD,EA9BG,aA8B0B4D,GARlDJ,EAAMK,EArBJ,QAqBIA,IAGLR,EAAiBrD,SAAS8D,EAAAA,EAAgBJ,GAC1C,IAJD5B,EAAArC,EAAA2B,UAcOgC,EAAYW,EAAUjC,EAAStD,aAAesD,EAASrD,YARxDuF,EAAAA,EACLR,EAAAA,gBACAA,EAAAA,eACAA,EAAAA,EAAmBS,QArDpB,SAAAjE,EAAAoD,GAsBKc,EATLlE,EAAMmD,UAAWgB,OAAXhB,GAGJnD,EAAAoD,UAAApD,EAAAoD,UACAgB,QAAA,IAAAC,OAAA,WAAAjB,EAAA,YAAA,KAWAgB,QAAQ,OAAQ,IAVjBpE,QAAAA,OAAQoD,IA8CRO,CAAY3D,EAAS8B,EAASvD,eAR/B4E,EAAMmB,EAAYlB,GACjBjC,EAAIW,EAAWrC,GAEfqB,EAAiBiD,GACdjC,IAKHqB,EAAA,SAAAnD,EAAAP,GACA0B,IAAAA,EAAcP,SAAdO,EAAcP,GAEdE,EAAAA,GAAkB,EAACrB,GACnBuE,EAbDhE,EAAA4D,EAAAF,IAeMa,EAAAA,SAAAA,EAAAA,GACLD,EAAMV,GAAc,EAAAnE,GACnB6E,EAAa/E,EAAaE,EAA1BiE,KA7BwB,SAAC1D,EAAS4D,EAAaF,GARjDL,EAAMA,EAfAM,OAeAN,GACLrD,EAAQqD,EAfe,aAevBO,GACAP,EAFDrD,EAbE,QAaF0D,GAuCEG,CAHD7D,EAAA4D,EAAAF,IAKCY,EAAa/E,CAAAA,MAAO,SAAOE,SAkBvB+E,EAAqB,SAACxE,EAASP,GAPpC,IAAAgF,EAAK3C,EAASpD,UACb8F,EAAAA,EAAkB/E,GAClBgF,GAAAhF,EAAA2B,UAAAzC,gBACA8F,EAAAC,UAAA1E,IAqBI2E,EAAkB,SAAA3E,GATxB,IAAA4E,EAAejE,EAACX,GACf4E,IAEAC,aAAK/C,GACJrB,EAAAT,EAAA,QAED2E,EAAAA,SAAe3E,EAAfP,GACA,IAPDqF,EAAArF,EAAA2B,UAAA1C,WAoBKkG,EAAYjE,EAAeX,GAXhC4E,IAECA,EAAKA,WAAW,WACfJ,EAAQxE,EAAAP,GACRkF,EAAA3E,IAcE8E,GAbHD,EAAY7E,EAAC4E,KAiBRG,EAAgB,SAAC/E,EAASP,EAAUuF,GAb1C,IAAAlD,EAAerC,EAAG2B,WACjB4D,GAAIF,EAAqB1D,KAEzB6D,EAAAC,QAAelF,EAAA6B,UAAA,IACd0C,EAAQvE,EAAAP,GACR0D,EAAAnD,EAAA8B,EAAAvD,gBACDqG,EAAAA,EAAYO,GArPH5E,SAAAA,GACRJ,EAAAH,EAjBC,gBAEF,QAoQCwE,CAAAA,GACAG,EAAAA,EAAgB3E,gBAAhBA,GACAmB,EAAE2D,EAHHM,aAAApF,KAYAqF,EAAgBH,SAAAA,GACfX,QAAAA,IAqBD9E,EAAS6F,UAAY,IAAIC,qBAAqB,SAAAC,GAlB9C9C,EAAAA,QAAW1C,SAAAA,GAAD,OAMY,SAAAyF,GAAK,OAf5BA,EAAMV,gBAAgBU,EAAhBV,kBAAiB/E,EAUtBU,CAAAA,GApEA,SAAAV,EAAAP,GAUA,IAAMqC,EAAWrC,EAAS2B,UAN1ByC,EAAAA,EAAkB7D,eAAS4D,GAT5B9B,EAAApD,WAcAgH,EAAMC,EAAUlG,GAId+E,EAAmBxE,EAASP,GAwD7B0B,CAAAA,EAAcW,OAAShD,GA1CT,SAACkB,EAASP,GARzB,IAAM+E,EAAAA,EAAqBpD,UAC1BD,EAAIsD,EAAWhF,cAAfO,GACA+E,EAAAA,YAECN,EAASC,GA+CVvD,CAAAA,EAAcW,OAASsD,MAMiB,CAhBxCQ,MAgB2B9D,EAeJrC,EAAS2B,WA/B5ByE,YAAUrF,SAAoBR,KAAU8B,EAAA+D,UAC3CC,WAD2ChE,EACnC7D,YAAA6D,EAAA9D,UAAA,QAYJ+H,GAGsB,IAAAjE,GAmBtBkE,EAAiB,CAAC,MAAO,UAoBzBC,EAAc,SAACtD,EAAUb,GAAX,OA5SQ,SAAAa,GAAO,OAAAA,EAClCrC,OAAQN,SAAAA,GAASE,OAAAA,EAAuBgG,KAuRzCC,EARGJ,EAQoBpD,GAJnByD,SAAAA,GAAoB3G,OACvBqC,EAAA+D,UAAAQ,iBAAAvE,EAAA/D,mBAGDuI,CAAAxE,GARkB2D,MACZE,UAAQF,MAAMxB,KAAQxE,KADzBsG,IAAAA,GAUGQ,EAAAA,SAAkBC,EAAlBD,GAA0B9I,KAAA2D,UA/UT,SAAAoF,GACtB,OAAIjH,SAAJ,GAAAzB,EAAA0I,GA+UA1E,CAAuB0E,GADQ/I,KAAhCsD,cAAA,EAwBCsE,EAAY5H,MArBbA,KAAMgJ,OAAAA,IAqCJ,OAlCCjJ,EAAAkJ,UAAA,CACAC,OAAA,SAAAhE,GAAA,IA5BiClD,EA4BjCmH,EAAAnJ,KAuBGqE,EAAWrE,KAAK2D,WAtBpBpB,KAAAA,UAAQ2B,EAAagB,EAAWb,IAChCiD,GAAAA,KAAc/E,YA9ByB,SAAA8B,GAAA,OACxC8D,EAAI1G,YAAW2G,YAAc3H,iBAAkB4D,UAiC1C+E,CAAc/E,MAlCgBrC,EAkCIiH,MAlCxCzF,UAAAmB,QAAA,SAAApC,IA0BmD,IAA7CgG,EAAed,QAAQlF,EAAQ6B,WAnBnC7B,EAAA2B,aAAA,UAAA,QACAoD,EAAA/E,EAAAP,MA0BFhC,KAAAwD,UAAAgF,EAAAtD,EAAAb,IAEArE,KAAM6I,UAAAA,QAAgB,SAAAtG,GAAQ4G,EAAAtB,UAC7BxD,QAAS+D,MANRpI,KANDqJ,WAcmBC,QAAA,WACnBZ,IAAAA,EAAAA,KADD1I,KAAA6H,YA6BG7H,KAAKwD,UAAUmB,QAAQ,SAAApC,GA1B1BgH,EAAMxJ,UAAWkH,UAAXlH,KAELC,KAAA6H,UAAKvE,MAELtD,KAAAwD,UAAY0B,KACZlF,KALD2D,UAAA,MAQCuF,KAAAA,SAAQ3G,EAAAgF,GAAmBD,EAAA/E,EAAAvC,KAAAuH,IAE1B8B,QAAA,WAAA,IAAAG,EAAAxJ,KA8BAA,KAAKwD,UAAUmB,QAAQ,SAAApC,GA7BvBwE,EAAcxE,EAAKsF,OAKlBmB,GArWH,SAAApH,EAAAC,GAGC,GAAKA,EADN,GAAAA,EAAS4H,OAIR,IAAA,IAAahG,EAAR5B,EAAAA,EAAgB6H,EAAA7H,EAAAmC,GAAAA,GAAA,EACpBrC,EAAAC,EAAA8H,QAHA/H,EAAAC,EAAAC,GAkWC4H,CAAKjG,EAAYgF,OAAAA,iBAElBzI","file":"lazyload.min.js","sourcesContent":["(function (global, factory) {\n\ttypeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n\ttypeof define === 'function' && define.amd ? define(factory) :\n\t(global.LazyLoad = factory());\n}(this, (function () { 'use strict';\n\nconst runningOnBrowser = typeof window !== \"undefined\";\r\n\r\nconst isBot =\r\n\t(runningOnBrowser && !(\"onscroll\" in window)) ||\r\n\t(typeof navigator !== \"undefined\" &&\r\n\t\t/(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent));\r\n\r\nconst supportsIntersectionObserver =\r\n\trunningOnBrowser && \"IntersectionObserver\" in window;\r\n\r\nconst supportsClassList =\r\n\trunningOnBrowser && \"classList\" in document.createElement(\"p\");\n\nconst defaultSettings = {\r\n\telements_selector: \"img\",\r\n\tcontainer: isBot || runningOnBrowser ? document : null,\r\n\tthreshold: 300,\r\n\tthresholds: null,\r\n\tdata_src: \"src\",\r\n\tdata_srcset: \"srcset\",\r\n\tdata_sizes: \"sizes\",\r\n\tdata_bg: \"bg\",\r\n\tclass_loading: \"loading\",\r\n\tclass_loaded: \"loaded\",\r\n\tclass_error: \"error\",\r\n\tload_delay: 0,\r\n\tauto_unobserve: true,\r\n\tcallback_enter: null,\r\n\tcallback_exit: null,\r\n\tcallback_reveal: null,\r\n\tcallback_loaded: null,\r\n\tcallback_error: null,\r\n\tcallback_finish: null,\r\n\tuse_native: false\r\n};\r\n\r\nvar getInstanceSettings = customSettings => {\r\n\treturn Object.assign({}, defaultSettings, customSettings);\r\n};\n\n/* Creates instance and notifies it through the window element */\r\nconst createInstance = function(classObj, options) {\r\n\tvar event;\r\n\tlet eventString = \"LazyLoad::Initialized\";\r\n\tlet instance = new classObj(options);\r\n\ttry {\r\n\t\t// Works in modern browsers\r\n\t\tevent = new CustomEvent(eventString, { detail: { instance } });\r\n\t} catch (err) {\r\n\t\t// Works in Internet Explorer (all versions)\r\n\t\tevent = document.createEvent(\"CustomEvent\");\r\n\t\tevent.initCustomEvent(eventString, false, false, { instance });\r\n\t}\r\n\twindow.dispatchEvent(event);\r\n};\r\n\r\n/* Auto initialization of one or more instances of lazyload, depending on the \r\n options passed in (plain object or an array) */\r\nfunction autoInitialize(classObj, options) {\r\n\tif (!options) {\r\n\t\treturn;\r\n\t}\r\n\tif (!options.length) {\r\n\t\t// Plain object\r\n\t\tcreateInstance(classObj, options);\r\n\t} else {\r\n\t\t// Array of objects\r\n\t\tfor (let i = 0, optionsItem; (optionsItem = options[i]); i += 1) {\r\n\t\t\tcreateInstance(classObj, optionsItem);\r\n\t\t}\r\n\t}\r\n}\n\nconst dataPrefix = \"data-\";\r\nconst processedDataName = \"was-processed\";\r\nconst timeoutDataName = \"ll-timeout\";\r\nconst trueString = \"true\";\r\n\r\nconst getData = (element, attribute) => {\r\n\treturn element.getAttribute(dataPrefix + attribute);\r\n};\r\n\r\nconst setData = (element, attribute, value) => {\r\n\tvar attrName = dataPrefix + attribute;\r\n\tif (value === null) {\r\n\t\telement.removeAttribute(attrName);\r\n\t\treturn;\r\n\t}\r\n\telement.setAttribute(attrName, value);\r\n};\r\n\r\nconst setWasProcessedData = element =>\r\n\tsetData(element, processedDataName, trueString);\r\n\r\nconst getWasProcessedData = element =>\r\n\tgetData(element, processedDataName) === trueString;\r\n\r\nconst setTimeoutData = (element, value) =>\r\n\tsetData(element, timeoutDataName, value);\r\n\r\nconst getTimeoutData = element => getData(element, timeoutDataName);\n\nconst purgeProcessedElements = elements => {\r\n\treturn elements.filter(element => !getWasProcessedData(element));\r\n};\r\n\r\nconst purgeOneElement = (elements, elementToPurge) => {\r\n\treturn elements.filter(element => element !== elementToPurge);\r\n};\n\nconst callbackIfSet = (callback, argument) => {\r\n\tif (callback) {\r\n\t\tcallback(argument);\r\n\t}\r\n};\n\nconst updateLoadingCount = (instance, plusMinus) => {\r\n\tinstance._loadingCount += plusMinus;\r\n\tif (instance._elements.length === 0 && instance._loadingCount === 0) {\r\n\t\tcallbackIfSet(instance._settings.callback_finish);\r\n\t}\r\n};\n\nconst getSourceTags = parentTag => {\r\n\tlet sourceTags = [];\r\n\tfor (let i = 0, childTag; (childTag = parentTag.children[i]); i += 1) {\r\n\t\tif (childTag.tagName === \"SOURCE\") {\r\n\t\t\tsourceTags.push(childTag);\r\n\t\t}\r\n\t}\r\n\treturn sourceTags;\r\n};\r\n\r\nconst setAttributeIfValue = (element, attrName, value) => {\r\n\tif (!value) {\r\n\t\treturn;\r\n\t}\r\n\telement.setAttribute(attrName, value);\r\n};\r\n\r\nconst setImageAttributes = (element, settings) => {\r\n\tsetAttributeIfValue(\r\n\t\telement,\r\n\t\t\"sizes\",\r\n\t\tgetData(element, settings.data_sizes)\r\n\t);\r\n\tsetAttributeIfValue(\r\n\t\telement,\r\n\t\t\"srcset\",\r\n\t\tgetData(element, settings.data_srcset)\r\n\t);\r\n\tsetAttributeIfValue(element, \"src\", getData(element, settings.data_src));\r\n};\r\n\r\nconst setSourcesImg = (element, settings) => {\r\n\tconst parent = element.parentNode;\r\n\r\n\tif (parent && parent.tagName === \"PICTURE\") {\r\n\t\tlet sourceTags = getSourceTags(parent);\r\n\t\tsourceTags.forEach(sourceTag => {\r\n\t\t\tsetImageAttributes(sourceTag, settings);\r\n\t\t});\r\n\t}\r\n\r\n\tsetImageAttributes(element, settings);\r\n};\r\n\r\nconst setSourcesIframe = (element, settings) => {\r\n\tsetAttributeIfValue(element, \"src\", getData(element, settings.data_src));\r\n};\r\n\r\nconst setSourcesVideo = (element, settings) => {\r\n\tlet sourceTags = getSourceTags(element);\r\n\tsourceTags.forEach(sourceTag => {\r\n\t\tsetAttributeIfValue(\r\n\t\t\tsourceTag,\r\n\t\t\t\"src\",\r\n\t\t\tgetData(sourceTag, settings.data_src)\r\n\t\t);\r\n\t});\r\n\tsetAttributeIfValue(element, \"src\", getData(element, settings.data_src));\r\n\telement.load();\r\n};\r\n\r\nconst setSourcesBgImage = (element, settings) => {\r\n\tconst srcDataValue = getData(element, settings.data_src);\r\n\tconst bgDataValue = getData(element, settings.data_bg);\r\n\r\n\tif (srcDataValue) {\r\n\t\telement.style.backgroundImage = `url(\"${srcDataValue}\")`;\r\n\t}\r\n\r\n\tif (bgDataValue) {\r\n\t\telement.style.backgroundImage = bgDataValue;\r\n\t}\r\n};\r\n\r\nconst setSourcesFunctions = {\r\n\tIMG: setSourcesImg,\r\n\tIFRAME: setSourcesIframe,\r\n\tVIDEO: setSourcesVideo\r\n};\r\n\r\nconst setSources = (element, instance) => {\r\n\tconst settings = instance._settings;\r\n\tconst tagName = element.tagName;\r\n\tconst setSourcesFunction = setSourcesFunctions[tagName];\r\n\tif (setSourcesFunction) {\r\n\t\tsetSourcesFunction(element, settings);\r\n\t\tupdateLoadingCount(instance, 1);\r\n\t\tinstance._elements = purgeOneElement(instance._elements, element);\r\n\t\treturn;\r\n\t}\r\n\tsetSourcesBgImage(element, settings);\r\n};\n\nconst addClass = (element, className) => {\r\n\tif (supportsClassList) {\r\n\t\telement.classList.add(className);\r\n\t\treturn;\r\n\t}\r\n\telement.className += (element.className ? \" \" : \"\") + className;\r\n};\r\n\r\nconst removeClass = (element, className) => {\r\n\tif (supportsClassList) {\r\n\t\telement.classList.remove(className);\r\n\t\treturn;\r\n\t}\r\n\telement.className = element.className.\r\n\t\treplace(new RegExp(\"(^|\\\\s+)\" + className + \"(\\\\s+|$)\"), \" \").\r\n\t\treplace(/^\\s+/, \"\").\r\n\t\treplace(/\\s+$/, \"\");\r\n};\n\nconst genericLoadEventName = \"load\";\r\nconst mediaLoadEventName = \"loadeddata\";\r\nconst errorEventName = \"error\";\r\n\r\nconst addEventListener = (element, eventName, handler) => {\r\n\telement.addEventListener(eventName, handler);\r\n};\r\n\r\nconst removeEventListener = (element, eventName, handler) => {\r\n\telement.removeEventListener(eventName, handler);\r\n};\r\n\r\nconst addEventListeners = (element, loadHandler, errorHandler) => {\r\n\taddEventListener(element, genericLoadEventName, loadHandler);\r\n\taddEventListener(element, mediaLoadEventName, loadHandler);\r\n\taddEventListener(element, errorEventName, errorHandler);\r\n};\r\n\r\nconst removeEventListeners = (element, loadHandler, errorHandler) => {\r\n\tremoveEventListener(element, genericLoadEventName, loadHandler);\r\n\tremoveEventListener(element, mediaLoadEventName, loadHandler);\r\n\tremoveEventListener(element, errorEventName, errorHandler);\r\n};\r\n\r\nconst eventHandler = function(event, success, instance) {\r\n\tvar settings = instance._settings;\r\n\tconst className = success ? settings.class_loaded : settings.class_error;\r\n\tconst callback = success\r\n\t\t? settings.callback_loaded\r\n\t\t: settings.callback_error;\r\n\tconst element = event.target;\r\n\r\n\tremoveClass(element, settings.class_loading);\r\n\taddClass(element, className);\r\n\tcallbackIfSet(callback, element);\r\n\r\n\tupdateLoadingCount(instance, -1);\r\n};\r\n\r\nconst addOneShotEventListeners = (element, instance) => {\r\n\tconst loadHandler = event => {\r\n\t\teventHandler(event, true, instance);\r\n\t\tremoveEventListeners(element, loadHandler, errorHandler);\r\n\t};\r\n\tconst errorHandler = event => {\r\n\t\teventHandler(event, false, instance);\r\n\t\tremoveEventListeners(element, loadHandler, errorHandler);\r\n\t};\r\n\taddEventListeners(element, loadHandler, errorHandler);\r\n};\n\nconst managedTags = [\"IMG\", \"IFRAME\", \"VIDEO\"];\r\n\r\nconst onEnter = (element, instance) => {\r\n\tconst settings = instance._settings;\r\n\tcallbackIfSet(settings.callback_enter, element);\r\n\tif (!settings.load_delay) {\r\n\t\trevealAndUnobserve(element, instance);\r\n\t\treturn;\r\n\t}\r\n\tdelayLoad(element, instance);\r\n};\r\n\r\nconst revealAndUnobserve = (element, instance) => {\r\n\tvar observer = instance._observer;\r\n\trevealElement(element, instance);\r\n\tif (observer && instance._settings.auto_unobserve) {\r\n\t\tobserver.unobserve(element);\r\n\t}\r\n};\r\n\r\nconst onExit = (element, instance) => {\r\n\tconst settings = instance._settings;\r\n\tcallbackIfSet(settings.callback_exit, element);\r\n\tif (!settings.load_delay) {\r\n\t\treturn;\r\n\t}\r\n\tcancelDelayLoad(element);\r\n};\r\n\r\nconst cancelDelayLoad = element => {\r\n\tvar timeoutId = getTimeoutData(element);\r\n\tif (!timeoutId) {\r\n\t\treturn; // do nothing if timeout doesn't exist\r\n\t}\r\n\tclearTimeout(timeoutId);\r\n\tsetTimeoutData(element, null);\r\n};\r\n\r\nconst delayLoad = (element, instance) => {\r\n\tvar loadDelay = instance._settings.load_delay;\r\n\tvar timeoutId = getTimeoutData(element);\r\n\tif (timeoutId) {\r\n\t\treturn; // do nothing if timeout already set\r\n\t}\r\n\ttimeoutId = setTimeout(function() {\r\n\t\trevealAndUnobserve(element, instance);\r\n\t\tcancelDelayLoad(element);\r\n\t}, loadDelay);\r\n\tsetTimeoutData(element, timeoutId);\r\n};\r\n\r\nconst revealElement = (element, instance, force) => {\r\n\tvar settings = instance._settings;\r\n\tif (!force && getWasProcessedData(element)) {\r\n\t\treturn; // element has already been processed and force wasn't true\r\n\t}\r\n\tif (managedTags.indexOf(element.tagName) > -1) {\r\n\t\taddOneShotEventListeners(element, instance);\r\n\t\taddClass(element, settings.class_loading);\r\n\t}\r\n\tsetSources(element, instance);\r\n\tsetWasProcessedData(element);\r\n\tcallbackIfSet(settings.callback_reveal, element);\r\n\tcallbackIfSet(settings.callback_set, element);\r\n};\n\nconst isIntersecting = entry =>\r\n\tentry.isIntersecting || entry.intersectionRatio > 0;\r\n\r\nconst getObserverSettings = settings => ({\r\n\troot: settings.container === document ? null : settings.container,\r\n\trootMargin: settings.thresholds || settings.threshold + \"px\"\r\n});\r\n\r\nconst setObserver = instance => {\r\n\tif (!supportsIntersectionObserver) {\r\n\t\treturn false;\r\n\t}\r\n\tinstance._observer = new IntersectionObserver(entries => {\r\n\t\tentries.forEach(entry =>\r\n\t\t\tisIntersecting(entry)\r\n\t\t\t\t? onEnter(entry.target, instance)\r\n\t\t\t\t: onExit(entry.target, instance)\r\n\t\t);\r\n\t}, getObserverSettings(instance._settings));\r\n\treturn true;\r\n};\n\nconst nativeLazyTags = [\"IMG\", \"IFRAME\"];\r\n\r\nconst shouldUseNative = settings =>\r\n\tsettings.use_native && \"loading\" in HTMLImageElement.prototype;\r\n\r\nconst loadAllNative = instance => {\r\n\tinstance._elements.forEach(element => {\r\n\t\tif (nativeLazyTags.indexOf(element.tagName) === -1) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\telement.setAttribute(\"loading\", \"lazy\");\r\n\t\trevealElement(element, instance);\r\n\t});\r\n};\n\nconst nodeSetToArray = nodeSet => Array.prototype.slice.call(nodeSet);\n\nconst queryElements = settings =>\r\n\tsettings.container.querySelectorAll(settings.elements_selector);\r\n\r\nconst getElements = (elements, settings) =>\r\n\tpurgeProcessedElements(nodeSetToArray(elements || queryElements(settings)));\n\nconst LazyLoad = function(customSettings, elements) {\r\n\tthis._settings = getInstanceSettings(customSettings);\r\n\tthis._loadingCount = 0;\r\n\tsetObserver(this);\r\n\tthis.update(elements);\r\n};\r\n\r\nLazyLoad.prototype = {\r\n\tupdate: function(elements) {\r\n\t\tvar settings = this._settings;\r\n\t\tthis._elements = getElements(elements, settings);\r\n\t\tif (isBot || !this._observer) {\r\n\t\t\tthis.loadAll();\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (shouldUseNative(settings)) {\r\n\t\t\tloadAllNative(this);\r\n\t\t\tthis._elements = getElements(elements, settings);\r\n\t\t}\r\n\t\tthis._elements.forEach(element => {\r\n\t\t\tthis._observer.observe(element);\r\n\t\t});\r\n\t},\r\n\r\n\tdestroy: function() {\r\n\t\tif (this._observer) {\r\n\t\t\tthis._elements.forEach(element => {\r\n\t\t\t\tthis._observer.unobserve(element);\r\n\t\t\t});\r\n\t\t\tthis._observer = null;\r\n\t\t}\r\n\t\tthis._elements = null;\r\n\t\tthis._settings = null;\r\n\t},\r\n\r\n\tload: function(element, force) {\r\n\t\trevealElement(element, this, force);\r\n\t},\r\n\r\n\tloadAll: function() {\r\n\t\tthis._elements.forEach(element => {\r\n\t\t\trevealAndUnobserve(element, this);\r\n\t\t});\r\n\t}\r\n};\r\n\r\n/* Automatic instances creation if required (useful for async script loading) */\r\nif (runningOnBrowser) {\r\n\tautoInitialize(LazyLoad, window.lazyLoadOptions);\r\n}\n\nreturn LazyLoad;\n\n})));\n"]}
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload/16.1/lazyload.js b/wp-content/plugins/wp-rocket/assets/js/lazyload/16.1/lazyload.js
new file mode 100644
index 00000000..e0d495a4
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload/16.1/lazyload.js
@@ -0,0 +1,733 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global = global || self, global.LazyLoad = factory());
+}(this, (function () { 'use strict';
+
+ function _extends() {
+ _extends = Object.assign || function (target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i];
+
+ for (var key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+
+ return target;
+ };
+
+ return _extends.apply(this, arguments);
+ }
+
+ var runningOnBrowser = typeof window !== "undefined";
+ var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent);
+ var supportsIntersectionObserver = runningOnBrowser && "IntersectionObserver" in window;
+ var supportsClassList = runningOnBrowser && "classList" in document.createElement("p");
+ var isHiDpi = runningOnBrowser && window.devicePixelRatio > 1;
+
+ var defaultSettings = {
+ elements_selector: "IMG",
+ container: isBot || runningOnBrowser ? document : null,
+ threshold: 300,
+ thresholds: null,
+ data_src: "src",
+ data_srcset: "srcset",
+ data_sizes: "sizes",
+ data_bg: "bg",
+ data_bg_hidpi: "bg-hidpi",
+ data_bg_multi: "bg-multi",
+ data_bg_multi_hidpi: "bg-multi-hidpi",
+ data_poster: "poster",
+ class_applied: "applied",
+ class_loading: "loading",
+ class_loaded: "loaded",
+ class_error: "error",
+ unobserve_completed: true,
+ unobserve_entered: false,
+ cancel_on_exit: false,
+ callback_enter: null,
+ callback_exit: null,
+ callback_applied: null,
+ callback_loading: null,
+ callback_loaded: null,
+ callback_error: null,
+ callback_finish: null,
+ callback_cancel: null,
+ use_native: false
+ };
+ var getExtendedSettings = function getExtendedSettings(customSettings) {
+ return _extends({}, defaultSettings, customSettings);
+ };
+
+ /* Creates instance and notifies it through the window element */
+ var createInstance = function createInstance(classObj, options) {
+ var event;
+ var eventString = "LazyLoad::Initialized";
+ var instance = new classObj(options);
+
+ try {
+ // Works in modern browsers
+ event = new CustomEvent(eventString, {
+ detail: {
+ instance: instance
+ }
+ });
+ } catch (err) {
+ // Works in Internet Explorer (all versions)
+ event = document.createEvent("CustomEvent");
+ event.initCustomEvent(eventString, false, false, {
+ instance: instance
+ });
+ }
+
+ window.dispatchEvent(event);
+ };
+ /* Auto initialization of one or more instances of lazyload, depending on the
+ options passed in (plain object or an array) */
+
+
+ var autoInitialize = function autoInitialize(classObj, options) {
+ if (!options) {
+ return;
+ }
+
+ if (!options.length) {
+ // Plain object
+ createInstance(classObj, options);
+ } else {
+ // Array of objects
+ for (var i = 0, optionsItem; optionsItem = options[i]; i += 1) {
+ createInstance(classObj, optionsItem);
+ }
+ }
+ };
+
+ var statusLoading = "loading";
+ var statusLoaded = "loaded";
+ var statusApplied = "applied";
+ var statusError = "error";
+ var statusNative = "native";
+
+ var dataPrefix = "data-";
+ var statusDataName = "ll-status";
+ var getData = function getData(element, attribute) {
+ return element.getAttribute(dataPrefix + attribute);
+ };
+ var setData = function setData(element, attribute, value) {
+ var attrName = dataPrefix + attribute;
+
+ if (value === null) {
+ element.removeAttribute(attrName);
+ return;
+ }
+
+ element.setAttribute(attrName, value);
+ };
+ var getStatus = function getStatus(element) {
+ return getData(element, statusDataName);
+ };
+ var setStatus = function setStatus(element, status) {
+ return setData(element, statusDataName, status);
+ };
+ var resetStatus = function resetStatus(element) {
+ return setStatus(element, null);
+ };
+ var hasEmptyStatus = function hasEmptyStatus(element) {
+ return getStatus(element) === null;
+ };
+ var hasStatusLoading = function hasStatusLoading(element) {
+ return getStatus(element) === statusLoading;
+ };
+ var hasStatusError = function hasStatusError(element) {
+ return getStatus(element) === statusError;
+ };
+ var hasStatusNative = function hasStatusNative(element) {
+ return getStatus(element) === statusNative;
+ };
+ var hadStartedLoading = function hadStartedLoading(element) {
+ return !hasEmptyStatus(element);
+ };
+
+ var safeCallback = function safeCallback(callback, arg1, arg2, arg3) {
+ if (!callback) {
+ return;
+ }
+
+ if (arg3 !== undefined) {
+ callback(arg1, arg2, arg3);
+ return;
+ }
+
+ if (arg2 !== undefined) {
+ callback(arg1, arg2);
+ return;
+ }
+
+ callback(arg1);
+ };
+
+ var addClass = function addClass(element, className) {
+ if (supportsClassList) {
+ element.classList.add(className);
+ return;
+ }
+
+ element.className += (element.className ? " " : "") + className;
+ };
+ var removeClass = function removeClass(element, className) {
+ if (supportsClassList) {
+ element.classList.remove(className);
+ return;
+ }
+
+ element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ").replace(/^\s+/, "").replace(/\s+$/, "");
+ };
+
+ var addTempImage = function addTempImage(element) {
+ element.llTempImage = document.createElement("IMG");
+ };
+ var deleteTempImage = function deleteTempImage(element) {
+ delete element.llTempImage;
+ };
+ var getTempImage = function getTempImage(element) {
+ return element.llTempImage;
+ };
+
+ var unobserve = function unobserve(element, instance) {
+ if (!instance) return;
+ var observer = instance._observer;
+ if (!observer) return;
+ observer.unobserve(element);
+ };
+ var resetObserver = function resetObserver(observer) {
+ observer.disconnect();
+ };
+ var unobserveIfRequired = function unobserveIfRequired(element, settings, instance) {
+ if (settings.unobserve_entered) unobserve(element, instance);
+ };
+
+ var updateLoadingCount = function updateLoadingCount(instance, delta) {
+ if (!instance) return;
+ instance.loadingCount += delta;
+ };
+ var decreaseToLoadCount = function decreaseToLoadCount(instance) {
+ if (!instance) return;
+ instance.toLoadCount -= 1;
+ };
+ var setToLoadCount = function setToLoadCount(instance, value) {
+ if (!instance) return;
+ instance.toLoadCount = value;
+ };
+ var isSomethingLoading = function isSomethingLoading(instance) {
+ return instance.loadingCount > 0;
+ };
+ var haveElementsToLoad = function haveElementsToLoad(instance) {
+ return instance.toLoadCount > 0;
+ };
+
+ var getSourceTags = function getSourceTags(parentTag) {
+ var sourceTags = [];
+
+ for (var i = 0, childTag; childTag = parentTag.children[i]; i += 1) {
+ if (childTag.tagName === "SOURCE") {
+ sourceTags.push(childTag);
+ }
+ }
+
+ return sourceTags;
+ };
+ var setAttributeIfValue = function setAttributeIfValue(element, attrName, value) {
+ if (!value) {
+ return;
+ }
+
+ element.setAttribute(attrName, value);
+ };
+ var resetAttribute = function resetAttribute(element, attrName) {
+ element.removeAttribute(attrName);
+ };
+ var hasOriginalAttributes = function hasOriginalAttributes(element) {
+ return !!element.llOriginalAttrs;
+ };
+ var saveOriginalImageAttributes = function saveOriginalImageAttributes(element) {
+ if (hasOriginalAttributes(element)) {
+ return;
+ }
+
+ var originalAttributes = {};
+ originalAttributes["src"] = element.getAttribute("src");
+ originalAttributes["srcset"] = element.getAttribute("srcset");
+ originalAttributes["sizes"] = element.getAttribute("sizes");
+ element.llOriginalAttrs = originalAttributes;
+ };
+ var restoreOriginalImageAttributes = function restoreOriginalImageAttributes(element) {
+ if (!hasOriginalAttributes(element)) {
+ return;
+ }
+
+ var originalAttributes = element.llOriginalAttrs;
+ setAttributeIfValue(element, "src", originalAttributes["src"]);
+ setAttributeIfValue(element, "srcset", originalAttributes["srcset"]);
+ setAttributeIfValue(element, "sizes", originalAttributes["sizes"]);
+ };
+ var setImageAttributes = function setImageAttributes(element, settings) {
+ setAttributeIfValue(element, "sizes", getData(element, settings.data_sizes));
+ setAttributeIfValue(element, "srcset", getData(element, settings.data_srcset));
+ setAttributeIfValue(element, "src", getData(element, settings.data_src));
+ };
+ var resetImageAttributes = function resetImageAttributes(element) {
+ resetAttribute(element, "src");
+ resetAttribute(element, "srcset");
+ resetAttribute(element, "sizes");
+ };
+ var forEachPictureSource = function forEachPictureSource(element, fn) {
+ var parent = element.parentNode;
+
+ if (!parent || parent.tagName !== "PICTURE") {
+ return;
+ }
+
+ var sourceTags = getSourceTags(parent);
+ sourceTags.forEach(fn);
+ };
+ var forEachVideoSource = function forEachVideoSource(element, fn) {
+ var sourceTags = getSourceTags(element);
+ sourceTags.forEach(fn);
+ };
+ var restoreOriginalAttributesImg = function restoreOriginalAttributesImg(element) {
+ forEachPictureSource(element, function (sourceTag) {
+ restoreOriginalImageAttributes(sourceTag);
+ });
+ restoreOriginalImageAttributes(element);
+ };
+ var setSourcesImg = function setSourcesImg(element, settings) {
+ forEachPictureSource(element, function (sourceTag) {
+ saveOriginalImageAttributes(sourceTag);
+ setImageAttributes(sourceTag, settings);
+ });
+ saveOriginalImageAttributes(element);
+ setImageAttributes(element, settings);
+ };
+ var resetSourcesImg = function resetSourcesImg(element) {
+ forEachPictureSource(element, function (sourceTag) {
+ resetImageAttributes(sourceTag);
+ });
+ resetImageAttributes(element);
+ };
+ var setSourcesIframe = function setSourcesIframe(element, settings) {
+ setAttributeIfValue(element, "src", getData(element, settings.data_src));
+ };
+ var setSourcesVideo = function setSourcesVideo(element, settings) {
+ forEachVideoSource(element, function (sourceTag) {
+ setAttributeIfValue(sourceTag, "src", getData(sourceTag, settings.data_src));
+ });
+ setAttributeIfValue(element, "poster", getData(element, settings.data_poster));
+ setAttributeIfValue(element, "src", getData(element, settings.data_src));
+ element.load();
+ };
+ var setSourcesFunctions = {
+ IMG: setSourcesImg,
+ IFRAME: setSourcesIframe,
+ VIDEO: setSourcesVideo
+ };
+ var setBackground = function setBackground(element, settings, instance) {
+ var bg1xValue = getData(element, settings.data_bg);
+ var bgHiDpiValue = getData(element, settings.data_bg_hidpi);
+ var bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue;
+ if (!bgDataValue) return;
+ element.style.backgroundImage = "url(\"".concat(bgDataValue, "\")");
+ getTempImage(element).setAttribute("src", bgDataValue);
+ manageLoading(element, settings, instance);
+ }; // NOTE: THE TEMP IMAGE TRICK CANNOT BE DONE WITH data-multi-bg
+ // BECAUSE INSIDE ITS VALUES MUST BE WRAPPED WITH URL() AND ONE OF THEM
+ // COULD BE A GRADIENT BACKGROUND IMAGE
+
+ var setMultiBackground = function setMultiBackground(element, settings, instance) {
+ var bg1xValue = getData(element, settings.data_bg_multi);
+ var bgHiDpiValue = getData(element, settings.data_bg_multi_hidpi);
+ var bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue;
+
+ if (!bgDataValue) {
+ return;
+ }
+
+ element.style.backgroundImage = bgDataValue;
+ manageApplied(element, settings, instance);
+ };
+ var setSources = function setSources(element, settings) {
+ var setSourcesFunction = setSourcesFunctions[element.tagName];
+
+ if (!setSourcesFunction) {
+ return;
+ }
+
+ setSourcesFunction(element, settings);
+ };
+ var manageApplied = function manageApplied(element, settings, instance) {
+ addClass(element, settings.class_applied);
+ setStatus(element, statusApplied);
+ removeDataMultiBackground(element, settings);
+
+ if (settings.unobserve_completed) {
+ // Unobserve now because we can't do it on load
+ unobserve(element, settings);
+ }
+
+ safeCallback(settings.callback_applied, element, instance);
+ };
+ var manageLoading = function manageLoading(element, settings, instance) {
+ updateLoadingCount(instance, +1);
+ addClass(element, settings.class_loading);
+ setStatus(element, statusLoading);
+ safeCallback(settings.callback_loading, element, instance);
+ }; // REMOVE DATA ATTRIBUTES --------------
+
+ var removeDataImg = function removeDataImg(element, settings) {
+ setData(element, settings.data_src, null);
+ setData(element, settings.data_srcset, null);
+ setData(element, settings.data_sizes, null);
+ forEachPictureSource(element, function (sourceTag) {
+ setData(sourceTag, settings.data_srcset, null);
+ setData(sourceTag, settings.data_sizes, null);
+ });
+ };
+ var removeDataIframe = function removeDataIframe(element, settings) {
+ setData(element, settings.data_src, null);
+ };
+ var removeDataVideo = function removeDataVideo(element, settings) {
+ setData(element, settings.data_src, null);
+ setData(element, settings.data_poster, null);
+ forEachVideoSource(element, function (sourceTag) {
+ setData(sourceTag, settings.data_src, null);
+ });
+ };
+ var removeDataFunctions = {
+ IMG: removeDataImg,
+ IFRAME: removeDataIframe,
+ VIDEO: removeDataVideo
+ };
+ var removeDataBackground = function removeDataBackground(element, settings) {
+ setData(element, settings.data_bg, null);
+ setData(element, settings.data_bg_hidpi, null);
+ };
+ var removeDataMultiBackground = function removeDataMultiBackground(element, settings) {
+ setData(element, settings.data_bg_multi, null);
+ setData(element, settings.data_bg_multi_hidpi, null);
+ };
+ var removeDataAttributes = function removeDataAttributes(element, settings) {
+ var removeDataFunction = removeDataFunctions[element.tagName];
+
+ if (removeDataFunction) {
+ removeDataFunction(element, settings);
+ return;
+ }
+
+ removeDataBackground(element, settings);
+ };
+
+ var elementsWithLoadEvent = ["IMG", "IFRAME", "VIDEO"];
+ var hasLoadEvent = function hasLoadEvent(element) {
+ return elementsWithLoadEvent.indexOf(element.tagName) > -1;
+ };
+ var checkFinish = function checkFinish(settings, instance) {
+ if (instance && !isSomethingLoading(instance) && !haveElementsToLoad(instance)) {
+ safeCallback(settings.callback_finish, instance);
+ }
+ };
+ var addEventListener = function addEventListener(element, eventName, handler) {
+ element.addEventListener(eventName, handler);
+ element.llEvLisnrs[eventName] = handler;
+ };
+ var removeEventListener = function removeEventListener(element, eventName, handler) {
+ element.removeEventListener(eventName, handler);
+ };
+ var hasEventListeners = function hasEventListeners(element) {
+ return !!element.llEvLisnrs;
+ };
+ var addEventListeners = function addEventListeners(element, loadHandler, errorHandler) {
+ if (!hasEventListeners(element)) element.llEvLisnrs = {};
+ var loadEventName = element.tagName === "VIDEO" ? "loadeddata" : "load";
+ addEventListener(element, loadEventName, loadHandler);
+ addEventListener(element, "error", errorHandler);
+ };
+ var removeEventListeners = function removeEventListeners(element) {
+ if (!hasEventListeners(element)) {
+ return;
+ }
+
+ var eventListeners = element.llEvLisnrs;
+
+ for (var eventName in eventListeners) {
+ var handler = eventListeners[eventName];
+ removeEventListener(element, eventName, handler);
+ }
+
+ delete element.llEvLisnrs;
+ };
+ var doneHandler = function doneHandler(element, settings, instance) {
+ deleteTempImage(element);
+ updateLoadingCount(instance, -1);
+ decreaseToLoadCount(instance);
+ removeClass(element, settings.class_loading);
+
+ if (settings.unobserve_completed) {
+ unobserve(element, instance);
+ }
+ };
+ var loadHandler = function loadHandler(event, element, settings, instance) {
+ var goingNative = hasStatusNative(element);
+ doneHandler(element, settings, instance);
+ addClass(element, settings.class_loaded);
+ setStatus(element, statusLoaded);
+ removeDataAttributes(element, settings);
+ safeCallback(settings.callback_loaded, element, instance);
+ if (!goingNative) checkFinish(settings, instance);
+ };
+ var errorHandler = function errorHandler(event, element, settings, instance) {
+ var goingNative = hasStatusNative(element);
+ doneHandler(element, settings, instance);
+ addClass(element, settings.class_error);
+ setStatus(element, statusError);
+ safeCallback(settings.callback_error, element, instance);
+ if (!goingNative) checkFinish(settings, instance);
+ };
+ var addOneShotEventListeners = function addOneShotEventListeners(element, settings, instance) {
+ var elementToListenTo = getTempImage(element) || element;
+
+ if (hasEventListeners(elementToListenTo)) {
+ // This happens when loading is retried twice
+ return;
+ }
+
+ var _loadHandler = function _loadHandler(event) {
+ loadHandler(event, element, settings, instance);
+ removeEventListeners(elementToListenTo);
+ };
+
+ var _errorHandler = function _errorHandler(event) {
+ errorHandler(event, element, settings, instance);
+ removeEventListeners(elementToListenTo);
+ };
+
+ addEventListeners(elementToListenTo, _loadHandler, _errorHandler);
+ };
+
+ var loadBackground = function loadBackground(element, settings, instance) {
+ addTempImage(element);
+ addOneShotEventListeners(element, settings, instance);
+ setBackground(element, settings, instance);
+ setMultiBackground(element, settings, instance);
+ };
+
+ var loadRegular = function loadRegular(element, settings, instance) {
+ addOneShotEventListeners(element, settings, instance);
+ setSources(element, settings);
+ manageLoading(element, settings, instance);
+ };
+
+ var load = function load(element, settings, instance) {
+ if (hasLoadEvent(element)) {
+ loadRegular(element, settings, instance);
+ } else {
+ loadBackground(element, settings, instance);
+ }
+ };
+ var loadNative = function loadNative(element, settings, instance) {
+ addOneShotEventListeners(element, settings, instance);
+ setSources(element, settings);
+ removeDataAttributes(element, settings);
+ setStatus(element, statusNative);
+ };
+
+ var cancelLoadingIfRequired = function cancelLoadingIfRequired(element, entry, settings, instance) {
+ if (!settings.cancel_on_exit) return;
+ if (!hasStatusLoading(element)) return;
+ if (element.tagName !== "IMG") return; //Works only on images
+
+ removeEventListeners(element);
+ resetSourcesImg(element);
+ restoreOriginalAttributesImg(element);
+ removeClass(element, settings.class_loading);
+ updateLoadingCount(instance, -1);
+ resetStatus(element);
+ safeCallback(settings.callback_cancel, element, entry, instance);
+ };
+
+ var onEnter = function onEnter(element, entry, settings, instance) {
+ safeCallback(settings.callback_enter, element, entry, instance);
+ unobserveIfRequired(element, settings, instance);
+ if (hadStartedLoading(element)) return; //Prevent loading it again
+
+ load(element, settings, instance);
+ };
+ var onExit = function onExit(element, entry, settings, instance) {
+ if (hasEmptyStatus(element)) return; //Ignore the first pass, at landing
+
+ cancelLoadingIfRequired(element, entry, settings, instance);
+ safeCallback(settings.callback_exit, element, entry, instance);
+ };
+
+ var tagsWithNativeLazy = ["IMG", "IFRAME"];
+ var shouldUseNative = function shouldUseNative(settings) {
+ return settings.use_native && "loading" in HTMLImageElement.prototype;
+ };
+ var loadAllNative = function loadAllNative(elements, settings, instance) {
+ elements.forEach(function (element) {
+ if (tagsWithNativeLazy.indexOf(element.tagName) === -1) {
+ return;
+ }
+
+ element.setAttribute("loading", "lazy"); //TODO: Move inside the loadNative method
+
+ loadNative(element, settings, instance);
+ });
+ setToLoadCount(instance, 0);
+ };
+
+ var isIntersecting = function isIntersecting(entry) {
+ return entry.isIntersecting || entry.intersectionRatio > 0;
+ };
+
+ var getObserverSettings = function getObserverSettings(settings) {
+ return {
+ root: settings.container === document ? null : settings.container,
+ rootMargin: settings.thresholds || settings.threshold + "px"
+ };
+ };
+
+ var intersectionHandler = function intersectionHandler(entries, settings, instance) {
+ entries.forEach(function (entry) {
+ return isIntersecting(entry) ? onEnter(entry.target, entry, settings, instance) : onExit(entry.target, entry, settings, instance);
+ });
+ };
+
+ var observeElements = function observeElements(observer, elements) {
+ elements.forEach(function (element) {
+ observer.observe(element);
+ });
+ };
+ var updateObserver = function updateObserver(observer, elementsToObserve) {
+ resetObserver(observer);
+ observeElements(observer, elementsToObserve);
+ };
+ var setObserver = function setObserver(settings, instance) {
+ if (!supportsIntersectionObserver || shouldUseNative(settings)) {
+ return;
+ }
+
+ instance._observer = new IntersectionObserver(function (entries) {
+ intersectionHandler(entries, settings, instance);
+ }, getObserverSettings(settings));
+ };
+
+ var toArray = function toArray(nodeSet) {
+ return Array.prototype.slice.call(nodeSet);
+ };
+ var queryElements = function queryElements(settings) {
+ return settings.container.querySelectorAll(settings.elements_selector);
+ };
+ var excludeManagedElements = function excludeManagedElements(elements) {
+ return toArray(elements).filter(hasEmptyStatus);
+ };
+ var hasError = function hasError(element) {
+ return hasStatusError(element);
+ };
+ var filterErrorElements = function filterErrorElements(elements) {
+ return toArray(elements).filter(hasError);
+ };
+ var getElementsToLoad = function getElementsToLoad(elements, settings) {
+ return excludeManagedElements(elements || queryElements(settings));
+ };
+
+ var retryLazyLoad = function retryLazyLoad(settings, instance) {
+ var errorElements = filterErrorElements(queryElements(settings));
+ errorElements.forEach(function (element) {
+ removeClass(element, settings.class_error);
+ resetStatus(element);
+ });
+ instance.update();
+ };
+ var setOnlineCheck = function setOnlineCheck(settings, instance) {
+ if (!runningOnBrowser) {
+ return;
+ }
+
+ window.addEventListener("online", function () {
+ retryLazyLoad(settings, instance);
+ });
+ };
+
+ var LazyLoad = function LazyLoad(customSettings, elements) {
+ var settings = getExtendedSettings(customSettings);
+ this._settings = settings;
+ this.loadingCount = 0;
+ setObserver(settings, this);
+ setOnlineCheck(settings, this);
+ this.update(elements);
+ };
+
+ LazyLoad.prototype = {
+ update: function update(givenNodeset) {
+ var settings = this._settings;
+ var elementsToLoad = getElementsToLoad(givenNodeset, settings);
+ setToLoadCount(this, elementsToLoad.length);
+
+ if (isBot || !supportsIntersectionObserver) {
+ this.loadAll(elementsToLoad);
+ return;
+ }
+
+ if (shouldUseNative(settings)) {
+ loadAllNative(elementsToLoad, settings, this);
+ return;
+ }
+
+ updateObserver(this._observer, elementsToLoad);
+ },
+ destroy: function destroy() {
+ // Observer
+ if (this._observer) {
+ this._observer.disconnect();
+ } // Clean custom attributes on elements
+
+
+ queryElements(this._settings).forEach(function (element) {
+ delete element.llOriginalAttrs;
+ }); // Delete all internal props
+
+ delete this._observer;
+ delete this._settings;
+ delete this.loadingCount;
+ delete this.toLoadCount;
+ },
+ loadAll: function loadAll(elements) {
+ var _this = this;
+
+ var settings = this._settings;
+ var elementsToLoad = getElementsToLoad(elements, settings);
+ elementsToLoad.forEach(function (element) {
+ load(element, settings, _this);
+ });
+ }
+ };
+
+ LazyLoad.load = function (element, customSettings) {
+ var settings = getExtendedSettings(customSettings);
+ load(element, settings);
+ };
+
+ LazyLoad.resetStatus = function (element) {
+ resetStatus(element);
+ }; // Automatic instances creation if required (useful for async script loading)
+
+
+ if (runningOnBrowser) {
+ autoInitialize(LazyLoad, window.lazyLoadOptions);
+ }
+
+ return LazyLoad;
+
+})));
diff --git a/wp-content/plugins/wp-rocket/assets/js/lazyload/16.1/lazyload.min.js b/wp-content/plugins/wp-rocket/assets/js/lazyload/16.1/lazyload.min.js
new file mode 100644
index 00000000..d4eee204
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/lazyload/16.1/lazyload.min.js
@@ -0,0 +1 @@
+!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).LazyLoad=n()}(this,(function(){"use strict";function t(){return(t=Object.assign||function(t){for(var n=1;n1,r={elements_selector:"IMG",container:e||n?document:null,threshold:300,thresholds:null,data_src:"src",data_srcset:"srcset",data_sizes:"sizes",data_bg:"bg",data_bg_hidpi:"bg-hidpi",data_bg_multi:"bg-multi",data_bg_multi_hidpi:"bg-multi-hidpi",data_poster:"poster",class_applied:"applied",class_loading:"loading",class_loaded:"loaded",class_error:"error",unobserve_completed:!0,unobserve_entered:!1,cancel_on_exit:!1,callback_enter:null,callback_exit:null,callback_applied:null,callback_loading:null,callback_loaded:null,callback_error:null,callback_finish:null,callback_cancel:null,use_native:!1},c=function(n){return t({},r,n)},l=function(t,n){var e,i=new t(n);try{e=new CustomEvent("LazyLoad::Initialized",{detail:{instance:i}})}catch(t){(e=document.createEvent("CustomEvent")).initCustomEvent("LazyLoad::Initialized",!1,!1,{instance:i})}window.dispatchEvent(e)},s=function(t,n){return t.getAttribute("data-"+n)},u=function(t,n,e){var i="data-"+n;null!==e?t.setAttribute(i,e):t.removeAttribute(i)},d=function(t){return s(t,"ll-status")},f=function(t,n){return u(t,"ll-status",n)},_=function(t){return f(t,null)},g=function(t){return null===d(t)},v=function(t){return"native"===d(t)},b=function(t,n,e,i){t&&(void 0===i?void 0===e?t(n):t(n,e):t(n,e,i))},p=function(t,n){a?t.classList.add(n):t.className+=(t.className?" ":"")+n},h=function(t,n){a?t.classList.remove(n):t.className=t.className.replace(new RegExp("(^|\\s+)"+n+"(\\s+|$)")," ").replace(/^\s+/,"").replace(/\s+$/,"")},m=function(t){return t.llTempImage},E=function(t,n){if(n){var e=n._observer;e&&e.unobserve(t)}},I=function(t,n){t&&(t.loadingCount+=n)},A=function(t,n){t&&(t.toLoadCount=n)},L=function(t){for(var n,e=[],i=0;n=t.children[i];i+=1)"SOURCE"===n.tagName&&e.push(n);return e},y=function(t,n,e){e&&t.setAttribute(n,e)},w=function(t,n){t.removeAttribute(n)},k=function(t){return!!t.llOriginalAttrs},z=function(t){if(!k(t)){var n={};n.src=t.getAttribute("src"),n.srcset=t.getAttribute("srcset"),n.sizes=t.getAttribute("sizes"),t.llOriginalAttrs=n}},O=function(t){if(k(t)){var n=t.llOriginalAttrs;y(t,"src",n.src),y(t,"srcset",n.srcset),y(t,"sizes",n.sizes)}},C=function(t,n){y(t,"sizes",s(t,n.data_sizes)),y(t,"srcset",s(t,n.data_srcset)),y(t,"src",s(t,n.data_src))},M=function(t){w(t,"src"),w(t,"srcset"),w(t,"sizes")},N=function(t,n){var e=t.parentNode;e&&"PICTURE"===e.tagName&&L(e).forEach(n)},x=function(t,n){L(t).forEach(n)},R={IMG:function(t,n){N(t,(function(t){z(t),C(t,n)})),z(t),C(t,n)},IFRAME:function(t,n){y(t,"src",s(t,n.data_src))},VIDEO:function(t,n){x(t,(function(t){y(t,"src",s(t,n.data_src))})),y(t,"poster",s(t,n.data_poster)),y(t,"src",s(t,n.data_src)),t.load()}},G=function(t,n){var e=R[t.tagName];e&&e(t,n)},T=function(t,n,e){I(e,1),p(t,n.class_loading),f(t,"loading"),b(n.callback_loading,t,e)},D={IMG:function(t,n){u(t,n.data_src,null),u(t,n.data_srcset,null),u(t,n.data_sizes,null),N(t,(function(t){u(t,n.data_srcset,null),u(t,n.data_sizes,null)}))},IFRAME:function(t,n){u(t,n.data_src,null)},VIDEO:function(t,n){u(t,n.data_src,null),u(t,n.data_poster,null),x(t,(function(t){u(t,n.data_src,null)}))}},F=function(t,n){u(t,n.data_bg_multi,null),u(t,n.data_bg_multi_hidpi,null)},V=function(t,n){var e=D[t.tagName];e?e(t,n):function(t,n){u(t,n.data_bg,null),u(t,n.data_bg_hidpi,null)}(t,n)},j=["IMG","IFRAME","VIDEO"],P=function(t,n){!n||function(t){return t.loadingCount>0}(n)||function(t){return t.toLoadCount>0}(n)||b(t.callback_finish,n)},S=function(t,n,e){t.addEventListener(n,e),t.llEvLisnrs[n]=e},U=function(t,n,e){t.removeEventListener(n,e)},$=function(t){return!!t.llEvLisnrs},q=function(t){if($(t)){var n=t.llEvLisnrs;for(var e in n){var i=n[e];U(t,e,i)}delete t.llEvLisnrs}},H=function(t,n,e){!function(t){delete t.llTempImage}(t),I(e,-1),function(t){t&&(t.toLoadCount-=1)}(e),h(t,n.class_loading),n.unobserve_completed&&E(t,e)},B=function(t,n,e){var i=m(t)||t;$(i)||function(t,n,e){$(t)||(t.llEvLisnrs={});var i="VIDEO"===t.tagName?"loadeddata":"load";S(t,i,n),S(t,"error",e)}(i,(function(a){!function(t,n,e,i){var a=v(n);H(n,e,i),p(n,e.class_loaded),f(n,"loaded"),V(n,e),b(e.callback_loaded,n,i),a||P(e,i)}(0,t,n,e),q(i)}),(function(a){!function(t,n,e,i){var a=v(n);H(n,e,i),p(n,e.class_error),f(n,"error"),b(e.callback_error,n,i),a||P(e,i)}(0,t,n,e),q(i)}))},J=function(t,n,e){!function(t){t.llTempImage=document.createElement("IMG")}(t),B(t,n,e),function(t,n,e){var i=s(t,n.data_bg),a=s(t,n.data_bg_hidpi),r=o&&a?a:i;r&&(t.style.backgroundImage='url("'.concat(r,'")'),m(t).setAttribute("src",r),T(t,n,e))}(t,n,e),function(t,n,e){var i=s(t,n.data_bg_multi),a=s(t,n.data_bg_multi_hidpi),r=o&&a?a:i;r&&(t.style.backgroundImage=r,function(t,n,e){p(t,n.class_applied),f(t,"applied"),F(t,n),n.unobserve_completed&&E(t,n),b(n.callback_applied,t,e)}(t,n,e))}(t,n,e)},K=function(t,n,e){!function(t){return j.indexOf(t.tagName)>-1}(t)?J(t,n,e):function(t,n,e){B(t,n,e),G(t,n),T(t,n,e)}(t,n,e)},Q=["IMG","IFRAME"],W=function(t){return t.use_native&&"loading"in HTMLImageElement.prototype},X=function(t,n,e){t.forEach((function(t){return function(t){return t.isIntersecting||t.intersectionRatio>0}(t)?function(t,n,e,i){b(e.callback_enter,t,n,i),function(t,n,e){n.unobserve_entered&&E(t,e)}(t,e,i),function(t){return!g(t)}(t)||K(t,e,i)}(t.target,t,n,e):function(t,n,e,i){g(t)||(function(t,n,e,i){e.cancel_on_exit&&function(t){return"loading"===d(t)}(t)&&"IMG"===t.tagName&&(q(t),function(t){N(t,(function(t){M(t)})),M(t)}(t),function(t){N(t,(function(t){O(t)})),O(t)}(t),h(t,e.class_loading),I(i,-1),_(t),b(e.callback_cancel,t,n,i))}(t,n,e,i),b(e.callback_exit,t,n,i))}(t.target,t,n,e)}))},Y=function(t){return Array.prototype.slice.call(t)},Z=function(t){return t.container.querySelectorAll(t.elements_selector)},tt=function(t){return function(t){return"error"===d(t)}(t)},nt=function(t,n){return function(t){return Y(t).filter(g)}(t||Z(n))},et=function(t,e){var a=c(t);this._settings=a,this.loadingCount=0,function(t,n){i&&!W(t)&&(n._observer=new IntersectionObserver((function(e){X(e,t,n)}),function(t){return{root:t.container===document?null:t.container,rootMargin:t.thresholds||t.threshold+"px"}}(t)))}(a,this),function(t,e){n&&window.addEventListener("online",(function(){!function(t,n){var e;(e=Z(t),Y(e).filter(tt)).forEach((function(n){h(n,t.class_error),_(n)})),n.update()}(t,e)}))}(a,this),this.update(e)};return et.prototype={update:function(t){var n,a,o=this._settings,r=nt(t,o);A(this,r.length),!e&&i?W(o)?function(t,n,e){t.forEach((function(t){-1!==Q.indexOf(t.tagName)&&(t.setAttribute("loading","lazy"),function(t,n,e){B(t,n,e),G(t,n),V(t,n),f(t,"native")}(t,n,e))})),A(e,0)}(r,o,this):(a=r,function(t){t.disconnect()}(n=this._observer),function(t,n){n.forEach((function(n){t.observe(n)}))}(n,a)):this.loadAll(r)},destroy:function(){this._observer&&this._observer.disconnect(),Z(this._settings).forEach((function(t){delete t.llOriginalAttrs})),delete this._observer,delete this._settings,delete this.loadingCount,delete this.toLoadCount},loadAll:function(t){var n=this,e=this._settings;nt(t,e).forEach((function(t){K(t,e,n)}))}},et.load=function(t,n){var e=c(n);K(t,e)},et.resetStatus=function(t){_(t)},n&&function(t,n){if(n)if(n.length)for(var e,i=0;e=n[i];i+=1)l(t,e);else l(t,n)}(et,window.lazyLoadOptions),et}));
diff --git a/wp-content/plugins/wp-rocket/assets/js/micromodal.min.js b/wp-content/plugins/wp-rocket/assets/js/micromodal.min.js
new file mode 100644
index 00000000..02ee0109
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/micromodal.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).MicroModal=t()}(this,function(){"use strict";return(()=>{const e=["a[href]","area[href]",'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',"select:not([disabled]):not([aria-hidden])","textarea:not([disabled]):not([aria-hidden])","button:not([disabled]):not([aria-hidden])","iframe","object","embed","[contenteditable]",'[tabindex]:not([tabindex^="-"])'];class t{constructor({targetModal:e,triggers:t=[],onShow:o=(()=>{}),onClose:i=(()=>{}),openTrigger:n="data-micromodal-trigger",closeTrigger:s="data-micromodal-close",disableScroll:a=!1,disableFocus:l=!1,awaitCloseAnimation:d=!1,awaitOpenAnimation:r=!1,debugMode:c=!1}){this.modal=document.getElementById(e),this.config={debugMode:c,disableScroll:a,openTrigger:n,closeTrigger:s,onShow:o,onClose:i,awaitCloseAnimation:d,awaitOpenAnimation:r,disableFocus:l},t.length>0&&this.registerTriggers(...t),this.onClick=this.onClick.bind(this),this.onKeydown=this.onKeydown.bind(this)}registerTriggers(...e){e.filter(Boolean).forEach(e=>{e.addEventListener("click",e=>this.showModal(e))})}showModal(){if(this.activeElement=document.activeElement,this.modal.setAttribute("aria-hidden","false"),this.modal.classList.add("is-open"),this.scrollBehaviour("disable"),this.addEventListeners(),this.config.awaitOpenAnimation){const e=()=>{this.modal.removeEventListener("animationend",e,!1),this.setFocusToFirstNode()};this.modal.addEventListener("animationend",e,!1)}else this.setFocusToFirstNode();this.config.onShow(this.modal,this.activeElement)}closeModal(){const e=this.modal;this.modal.setAttribute("aria-hidden","true"),this.removeEventListeners(),this.scrollBehaviour("enable"),this.activeElement&&this.activeElement.focus(),this.config.onClose(this.modal),this.config.awaitCloseAnimation?this.modal.addEventListener("animationend",function t(){e.classList.remove("is-open"),e.removeEventListener("animationend",t,!1)},!1):e.classList.remove("is-open")}closeModalById(e){this.modal=document.getElementById(e),this.modal&&this.closeModal()}scrollBehaviour(e){if(!this.config.disableScroll)return;const t=document.querySelector("body");switch(e){case"enable":Object.assign(t.style,{overflow:"",height:""});break;case"disable":Object.assign(t.style,{overflow:"hidden",height:"100vh"})}}addEventListeners(){this.modal.addEventListener("touchstart",this.onClick),this.modal.addEventListener("click",this.onClick),document.addEventListener("keydown",this.onKeydown)}removeEventListeners(){this.modal.removeEventListener("touchstart",this.onClick),this.modal.removeEventListener("click",this.onClick),document.removeEventListener("keydown",this.onKeydown)}onClick(e){e.target.hasAttribute(this.config.closeTrigger)&&(this.closeModal(),e.preventDefault())}onKeydown(e){27===e.keyCode&&this.closeModal(e),9===e.keyCode&&this.maintainFocus(e)}getFocusableNodes(){const t=this.modal.querySelectorAll(e);return Array(...t)}setFocusToFirstNode(){if(this.config.disableFocus)return;const e=this.getFocusableNodes();e.length&&e[0].focus()}maintainFocus(e){const t=this.getFocusableNodes();if(this.modal.contains(document.activeElement)){const o=t.indexOf(document.activeElement);e.shiftKey&&0===o&&(t[t.length-1].focus(),e.preventDefault()),e.shiftKey||o!==t.length-1||(t[0].focus(),e.preventDefault())}else t[0].focus()}}let o=null;const i=e=>{if(!document.getElementById(e))return console.warn(`MicroModal: âSeems like you have missed %c'${e}'`,"background-color: #f8f9fa;color: #50596c;font-weight: bold;","ID somewhere in your code. Refer example below to resolve it."),console.warn("%cExample:","background-color: #f8f9fa;color: #50596c;font-weight: bold;",`
`),!1},n=(e,t)=>{if((e=>{if(e.length<=0)console.warn("MicroModal: âPlease specify at least one %c'micromodal-trigger'","background-color: #f8f9fa;color: #50596c;font-weight: bold;","data attribute."),console.warn("%cExample:","background-color: #f8f9fa;color: #50596c;font-weight: bold;",' ')})(e),!t)return!0;for(var o in t)i(o);return!0};return{init:e=>{const i=Object.assign({},{openTrigger:"data-micromodal-trigger"},e),s=[...document.querySelectorAll(`[${i.openTrigger}]`)],a=((e,t)=>{const o=[];return e.forEach(e=>{const i=e.attributes[t].value;void 0===o[i]&&(o[i]=[]),o[i].push(e)}),o})(s,i.openTrigger);if(!0!==i.debugMode||!1!==n(s,a))for(var l in a){let e=a[l];i.targetModal=l,i.triggers=[...e],o=new t(i)}},show:(e,n)=>{const s=n||{};s.targetModal=e,!0===s.debugMode&&!1===i(e)||(o=new t(s)).showModal()},close:e=>{e?o.closeModalById(e):o.closeModal()}}})()});
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/js/preload-links.js b/wp-content/plugins/wp-rocket/assets/js/preload-links.js
new file mode 100644
index 00000000..436d0138
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/preload-links.js
@@ -0,0 +1,255 @@
+class RocketPreloadLinks {
+
+ constructor( browser, config ) {
+ this.browser = browser;
+ this.config = config;
+ this.options = this.browser.options;
+
+ this.prefetched = new Set;
+ this.eventTime = null;
+ this.threshold = 1111;
+ this.numOnHover = 0;
+ }
+
+ /**
+ * Initializes the handler.
+ */
+ init() {
+ if (
+ ! this.browser.supportsLinkPrefetch()
+ ||
+ this.browser.isDataSaverModeOn()
+ ||
+ this.browser.isSlowConnection()
+ ) {
+ return;
+ }
+
+ this.regex = {
+ excludeUris: RegExp( this.config.excludeUris, 'i' ),
+ images: RegExp( '.(' + this.config.imageExt + ')$', 'i' ),
+ fileExt: RegExp( '.(' + this.config.fileExt + ')$', 'i' )
+ };
+
+ this._initListeners( this );
+ }
+
+ /**
+ * Initializes the event listeners.
+ *
+ * @private
+ *
+ * @param self instance of this object, used for binding "this" to the listeners.
+ */
+ _initListeners( self ) {
+ // Setting onHoverDelay to -1 disables the "on-hover" feature.
+ if ( this.config.onHoverDelay > -1 ) {
+ document.addEventListener( 'mouseover', self.listener.bind( self ), self.listenerOptions );
+ }
+
+ document.addEventListener( 'mousedown', self.listener.bind( self ), self.listenerOptions );
+ document.addEventListener( 'touchstart', self.listener.bind( self ), self.listenerOptions );
+ }
+
+ /**
+ * Event listener. Processes when near or on a valid hyperlink.
+ *
+ * @param Event event Event instance.
+ */
+ listener( event ) {
+ const linkElem = event.target.closest( 'a' );
+ const url = this._prepareUrl( linkElem );
+ if ( null === url ) {
+ return;
+ }
+
+ switch ( event.type ) {
+ case 'mousedown':
+ case 'touchstart':
+ this._addPrefetchLink( url );
+ break;
+ case 'mouseover':
+ this._earlyPrefetch( linkElem, url, 'mouseout' );
+ }
+ }
+
+ /**
+ *
+ * @private
+ *
+ * @param Element|null linkElem
+ * @param object url
+ * @param string resetEvent
+ */
+ _earlyPrefetch( linkElem, url, resetEvent ) {
+ const doPrefetch = () => {
+ falseTrigger = null;
+
+ // Start the rate throttle: 1 sec timeout.
+ if ( 0 === this.numOnHover ) {
+ setTimeout( () => this.numOnHover = 0, 1000 );
+ }
+ // Bail out when exceeding the rate throttle.
+ else if ( this.numOnHover > this.config.rateThrottle ) {
+ return;
+ }
+
+ this.numOnHover++;
+ this._addPrefetchLink( url );
+ };
+
+ // Delay to avoid false triggers for hover/touch/tap.
+ let falseTrigger = setTimeout( doPrefetch, this.config.onHoverDelay );
+
+ // On reset event, reset the false trigger timer.
+ const reset = () => {
+ linkElem.removeEventListener( resetEvent, reset, { passive: true } );
+ if ( null === falseTrigger ) {
+ return;
+ }
+
+ clearTimeout( falseTrigger );
+ falseTrigger = null;
+ };
+ linkElem.addEventListener( resetEvent, reset, { passive: true } );
+ }
+
+ /**
+ * Adds a for the given URL.
+ *
+ * @param string url The Given URL to prefetch.
+ */
+ _addPrefetchLink( url ) {
+ this.prefetched.add( url.href );
+
+ return new Promise( ( resolve, reject ) => {
+ const elem = document.createElement( 'link' );
+ elem.rel = 'prefetch';
+ elem.href = url.href;
+ elem.onload = resolve;
+ elem.onerror = reject;
+
+ document.head.appendChild( elem );
+ } ).catch(() => {
+ // ignore and continue.
+ });
+ }
+
+ /**
+ * Prepares the target link's URL.
+ *
+ * @private
+ *
+ * @param Element|null linkElem Instance of the link element.
+ * @returns {null|*}
+ */
+ _prepareUrl( linkElem ) {
+ if (
+ null === linkElem
+ ||
+ typeof linkElem !== 'object'
+ ||
+ ! 'href' in linkElem
+ ||
+ // Link prefetching only works on http/https protocol.
+ [ 'http:', 'https:' ].indexOf( linkElem.protocol ) === -1
+ ) {
+ return null;
+ }
+
+ const origin = linkElem.href.substring( 0, this.config.siteUrl.length );
+ const pathname = this._getPathname( linkElem.href, origin );
+ const url = {
+ original: linkElem.href,
+ protocol: linkElem.protocol,
+ origin: origin,
+ pathname: pathname,
+ href: origin + pathname
+ };
+
+ return this._isLinkOk( url ) ? url : null;
+ }
+
+ /**
+ * Gets the URL's pathname. Note: ensures the pathname matches the permalink structure.
+ *
+ * @private
+ *
+ * @param object url Instance of the URL.
+ * @param string origin The target link href's origin.
+ * @returns {string}
+ */
+ _getPathname( url, origin ) {
+ let pathname = origin
+ ? url.substring( this.config.siteUrl.length )
+ : url;
+
+ if ( ! pathname.startsWith( '/' ) ) {
+ pathname = '/' + pathname;
+ }
+
+ if ( this._shouldAddTrailingSlash( pathname ) ) {
+ return pathname + '/';
+ }
+
+ return pathname;
+ }
+
+ _shouldAddTrailingSlash( pathname ) {
+ return (
+ this.config.usesTrailingSlash
+ &&
+ ! pathname.endsWith( '/' )
+ &&
+ ! this.regex.fileExt.test( pathname )
+ );
+ }
+
+ /**
+ * Checks if the given link element is okay to process.
+ *
+ * @private
+ *
+ * @param object url URL parts object.
+ *
+ * @returns {boolean}
+ */
+ _isLinkOk( url ) {
+ if ( null === url || typeof url !== 'object' ) {
+ return false;
+ }
+
+ return (
+ ! this.prefetched.has( url.href )
+ &&
+ url.origin === this.config.siteUrl // is an internal document.
+ &&
+ url.href.indexOf( '?' ) === -1 // not a query string.
+ &&
+ url.href.indexOf( '#' ) === -1 // not an anchor.
+ &&
+ ! this.regex.excludeUris.test( url.href ) // not excluded.
+ &&
+ ! this.regex.images.test( url.href ) // not an image.
+ );
+ }
+
+ /**
+ * Named static constructor to encapsulate how to create the object.
+ */
+ static run() {
+ // Bail out if the configuration not passed from the server.
+ if ( typeof RocketPreloadLinksConfig === 'undefined' ) {
+ return;
+ }
+
+ const browser = new RocketBrowserCompatibilityChecker( {
+ capture: true,
+ passive: true
+ } );
+ const instance = new RocketPreloadLinks( browser, RocketPreloadLinksConfig );
+ instance.init();
+ }
+}
+
+RocketPreloadLinks.run();
diff --git a/wp-content/plugins/wp-rocket/assets/js/preload-links.min.js b/wp-content/plugins/wp-rocket/assets/js/preload-links.min.js
new file mode 100644
index 00000000..49e0a543
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/preload-links.min.js
@@ -0,0 +1,3 @@
+(function() {
+"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e=function(){function i(e,t){for(var n=0;ni.config.rateThrottle)return;i.numOnHover++,i._addPrefetchLink(e)},this.config.onHoverDelay);t.addEventListener(n,function e(){t.removeEventListener(n,e,{passive:!0}),null!==r&&(clearTimeout(r),r=null)},{passive:!0})}},{key:"_addPrefetchLink",value:function(i){return this.prefetched.add(i.href),new Promise(function(e,t){var n=document.createElement("link");n.rel="prefetch",n.href=i.href,n.onload=e,n.onerror=t,document.head.appendChild(n)}).catch(function(){})}},{key:"_prepareUrl",value:function(e){if(null===e||"object"!==(void 0===e?"undefined":r(e))||!1 in e||-1===["http:","https:"].indexOf(e.protocol))return null;var t=e.href.substring(0,this.config.siteUrl.length),n=this._getPathname(e.href,t),i={original:e.href,protocol:e.protocol,origin:t,pathname:n,href:t+n};return this._isLinkOk(i)?i:null}},{key:"_getPathname",value:function(e,t){var n=t?e.substring(this.config.siteUrl.length):e;return n.startsWith("/")||(n="/"+n),this._shouldAddTrailingSlash(n)?n+"/":n}},{key:"_shouldAddTrailingSlash",value:function(e){return this.config.usesTrailingSlash&&!e.endsWith("/")&&!this.regex.fileExt.test(e)}},{key:"_isLinkOk",value:function(e){return null!==e&&"object"===(void 0===e?"undefined":r(e))&&(!this.prefetched.has(e.href)&&e.origin===this.config.siteUrl&&-1===e.href.indexOf("?")&&-1===e.href.indexOf("#")&&!this.regex.excludeUris.test(e.href)&&!this.regex.images.test(e.href))}}],[{key:"run",value:function(){"undefined"!=typeof RocketPreloadLinksConfig&&new n(new RocketBrowserCompatibilityChecker({capture:!0,passive:!0}),RocketPreloadLinksConfig).init()}}]),n}();t.run();
+}());
diff --git a/wp-content/plugins/wp-rocket/assets/js/wpr-admin-common.js b/wp-content/plugins/wp-rocket/assets/js/wpr-admin-common.js
new file mode 100644
index 00000000..23ce05dc
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/wpr-admin-common.js
@@ -0,0 +1,7 @@
+jQuery( document ).ready( function( $ ){
+ $( '.rocket-dismiss' ).on( 'click', function( e ) {
+ e.preventDefault();
+ var url = $( this ).attr( 'href' ).replace( 'admin-post', 'admin-ajax' );
+ $.get( url ).done( $( this ).closest( '.notice' ).hide( 'slow' ) );
+ });
+} );
diff --git a/wp-content/plugins/wp-rocket/assets/js/wpr-admin.js b/wp-content/plugins/wp-rocket/assets/js/wpr-admin.js
new file mode 100644
index 00000000..2e4fe7a4
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/wpr-admin.js
@@ -0,0 +1,2 @@
+!function r(a,o,l){function h(e,t){if(!o[e]){if(!a[e]){var i="function"==typeof require&&require;if(!t&&i)return i(e,!0);if(c)return c(e,!0);var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}var s=o[e]={exports:{}};a[e][0].call(s.exports,function(t){return h(a[e][1][t]||t)},s,s.exports,r,a,o,l)}return o[e].exports}for(var c="function"==typeof require&&require,t=0;t form > #wpr-options-submit"),this.$pages=document.querySelectorAll(".wpr-Page"),this.$sidebar=document.querySelector(".wpr-Sidebar"),this.$content=document.querySelector(".wpr-Content"),this.$tips=document.querySelector(".wpr-Content-tips"),this.$links=document.querySelectorAll(".wpr-body a"),this.$menuItem=null,this.$page=null,this.pageId=null,this.bodyTop=0,this.buttonText=this.$submitButton.value,i.getBodyTop(),window.onhashchange=function(){i.detectID()},window.location.hash?(this.bodyTop=0,this.detectID()):(e=localStorage.getItem("wpr-hash"),this.bodyTop=0,e?(window.location.hash=e,this.detectID()):(this.$menuItems[0].classList.add("isActive"),localStorage.setItem("wpr-hash","dashboard"),window.location.hash="#dashboard"));for(var n=0;nl;l++)i.startAt&&(i.startAt=d(i.startAt)),h.to(t[l],e,d(i),l*n);return this.add(h,s)},t.staggerFrom=function(t,e,i,n,s,r,a,o){return i.immediateRender=0!=i.immediateRender,i.runBackwards=!0,this.staggerTo(t,e,i,n,s,r,a,o)},t.staggerFromTo=function(t,e,i,n,s,r,a,o,l){return n.startAt=i,n.immediateRender=0!=n.immediateRender&&0!=i.immediateRender,this.staggerTo(t,e,n,s,r,a,o,l)},t.call=function(t,e,i,n){return this.add(p.delayedCall(0,t,e,i),n)},t.set=function(t,e,i){return i=this._parseTimeOrLabel(i,0,!0),null==e.immediateRender&&(e.immediateRender=i===this._time&&!this._paused),this.add(new p(t,0,e),i)},f.exportRoot=function(t,e){null==(t=t||{}).smoothChildTiming&&(t.smoothChildTiming=!0);var i,n,s=new f(t),r=s._timeline;for(null==e&&(e=!0),r._remove(s,!0),s._startTime=0,s._rawPrevTime=s._time=s._totalTime=r._time,i=r._first;i;)n=i._next,e&&i instanceof p&&i.target===i.vars.onComplete||s.add(i,i._startTime-i._delay),i=n;return r.add(s,0),s},t.add=function(t,e,i,n){var s,r,a,o,l,h;if("number"!=typeof e&&(e=this._parseTimeOrLabel(e,0,!0,t)),!(t instanceof c)){if(t instanceof Array||t&&t.push&&g(t)){for(i=i||"normal",n=n||0,s=e,r=t.length,a=0;at._startTime;l._timeline;)h&&l._timeline.smoothChildTiming?l.totalTime(l._totalTime,!0):l._gc&&l._enabled(!0,!1),l=l._timeline;return this},t.remove=function(t){if(t instanceof c)return this._remove(t,!1);if(t instanceof Array||t&&t.push&&g(t)){for(var e=t.length;-1<--e;)this.remove(t[e]);return this}return"string"==typeof t?this.removeLabel(t):this.kill(null,t)},t._remove=function(t,e){u.prototype._remove.call(this,t,e);var i=this._last;return i?this._time>i._startTime+i._totalDuration/i._timeScale&&(this._time=this.duration(),this._totalTime=this._totalDuration):this._time=this._totalTime=this._duration=this._totalDuration=0,this},t.append=function(t,e){return this.add(t,this._parseTimeOrLabel(null,e,!0,t))},t.insert=t.insertMultiple=function(t,e,i,n){return this.add(t,e||0,i,n)},t.appendMultiple=function(t,e,i,n){return this.add(t,this._parseTimeOrLabel(null,e,!0,t),i,n)},t.addLabel=function(t,e){return this._labels[t]=this._parseTimeOrLabel(e),this},t.addPause=function(t,e,i,n){return this.call(s,["{self}",e,i,n],this,t)},t.removeLabel=function(t){return delete this._labels[t],this},t.getLabelTime=function(t){return null!=this._labels[t]?this._labels[t]:-1},t._parseTimeOrLabel=function(t,e,i,n){var s;if(n instanceof c&&n.timeline===this)this.remove(n);else if(n&&(n instanceof Array||n.push&&g(n)))for(s=n.length;-1<--s;)n[s]instanceof c&&n[s].timeline===this&&this.remove(n[s]);if("string"==typeof e)return this._parseTimeOrLabel(e,i&&"number"==typeof t&&null==this._labels[e]?t-this.duration():0,i);if(e=e||0,"string"!=typeof t||!isNaN(t)&&null==this._labels[t])null==t&&(t=this.duration());else{if(-1===(s=t.indexOf("=")))return null==this._labels[t]?i?this._labels[t]=this.duration()+e:e:this._labels[t]+e;e=parseInt(t.charAt(s-1)+"1",10)*Number(t.substr(s+1)),t=1_&&(a="onReverseComplete"))),this._rawPrevTime=this._duration||!e||t||this._rawPrevTime===t?t:_,t=l+1e-4):t<1e-7?(((this._totalTime=this._time=0)!==h||0===this._duration&&this._rawPrevTime!==_&&(0=h)for(n=this._first;n&&(r=n._next,!this._paused||p);)(n._active||n._startTime<=this._time&&!n._paused&&!n._gc)&&(n._reversed?n.render((n._dirty?n.totalDuration():n._totalDuration)-(t-n._startTime)*n._timeScale,e,i):n.render((t-n._startTime)*n._timeScale,e,i)),n=r;else for(n=this._last;n&&(r=n._prev,!this._paused||p);)(n._active||h>=n._startTime&&!n._paused&&!n._gc)&&(n._reversed?n.render((n._dirty?n.totalDuration():n._totalDuration)-(t-n._startTime)*n._timeScale,e,i):n.render((t-n._startTime)*n._timeScale,e,i)),n=r;this._onUpdate&&(e||this._onUpdate.apply(this.vars.onUpdateScope||this,this.vars.onUpdateParams||v)),a&&(this._gc||c!==this._startTime&&u===this._timeScale||!(0===this._time||l>=this.totalDuration())||(s&&(this._timeline.autoRemoveChildren&&this._enabled(!1,!1),this._active=!1),!e&&this.vars[a]&&this.vars[a].apply(this.vars[a+"Scope"]||this,this.vars[a+"Params"]||v)))}},t._hasPausedChild=function(){for(var t=this._first;t;){if(t._paused||t instanceof f&&t._hasPausedChild())return!0;t=t._next}return!1},t.getChildren=function(t,e,i,n){n=n||-9999999999;for(var s=[],r=this._first,a=0;r;)n>r._startTime||(r instanceof p?!1!==e&&(s[a++]=r):(!1!==i&&(s[a++]=r),!1!==t&&(a=(s=s.concat(r.getChildren(!0,e,i))).length))),r=r._next;return s},t.getTweensOf=function(t,e){var i,n,s=this._gc,r=[],a=0;for(s&&this._enabled(!0,!0),n=(i=p.getTweensOf(t)).length;-1<--n;)(i[n].timeline===this||e&&this._contains(i[n]))&&(r[a++]=i[n]);return s&&this._enabled(!1,!0),r},t._contains=function(t){for(var e=t.timeline;e;){if(e===this)return!0;e=e.timeline}return!1},t.shiftChildren=function(t,e,i){i=i||0;for(var n,s=this._first,r=this._labels;s;)s._startTime>=i&&(s._startTime+=t),s=s._next;if(e)for(n in r)r[n]>=i&&(r[n]+=t);return this._uncache(!0)},t._kill=function(t,e){if(!t&&!e)return this._enabled(!1,!1);for(var i=e?this.getTweensOf(e):this.getChildren(!0,!0,!1),n=i.length,s=!1;-1<--n;)i[n]._kill(t,e)&&(s=!0);return s},t.clear=function(t){var e=this.getChildren(!1,!0,!0),i=e.length;for(this._time=this._totalTime=0;-1<--i;)e[i]._enabled(!1,!1);return!1!==t&&(this._labels={}),this._uncache(!0)},t.invalidate=function(){for(var t=this._first;t;)t.invalidate(),t=t._next;return this},t._enabled=function(t,e){if(t===this._gc)for(var i=this._first;i;)i._enabled(t,!0),i=i._next;return u.prototype._enabled.call(this,t,e)},t.duration=function(t){return arguments.length?(0!==this.duration()&&0!==t&&this.timeScale(this._duration/t),this):(this._dirty&&this.totalDuration(),this._duration)},t.totalDuration=function(t){if(arguments.length)return 0!==this.totalDuration()&&0!==t&&this.timeScale(this._totalDuration/t),this;if(this._dirty){for(var e,i,n=0,s=this._last,r=999999999999;s;)e=s._prev,s._dirty&&s.totalDuration(),s._startTime>r&&this._sortChildren&&!s._paused?this.add(s,s._startTime-s._delay):r=s._startTime,s._startTime<0&&!s._paused&&(n-=s._startTime,this._timeline.smoothChildTiming&&(this._startTime+=s._startTime/this._timeScale),this.shiftChildren(-s._startTime,!1,-9999999999),r=0),n<(i=s._startTime+s._totalDuration/s._timeScale)&&(n=i),s=e;this._duration=this._totalDuration=n,this._dirty=!1}return this._totalDuration},t.usesFrames=function(){for(var t=this._timeline;t._timeline;)t=t._timeline;return t===c._rootFramesTimeline},t.rawTime=function(){return this._paused?this._totalTime:(this._timeline.rawTime()-this._startTime)*this._timeScale},f},!0)}),window._gsDefine&&window._gsQueue.pop()()},{}],10:[function(t,W,e){"use strict";var Q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};!function(f){var e,i,d=f.GreenSockGlobals||f;if(!d.TweenLite){var _,m=function(t){for(var e=t.split("."),i=d,n=0;e.length>n;n++)i[e[n]]=i=i[e[n]]||{};return i},u=m("com.greensock"),g=1e-10,l=[].slice,n=function(){},c=(e=Object.prototype.toString,i=e.call([]),function(t){return null!=t&&(t instanceof Array||"object"==(void 0===t?"undefined":Q(t))&&!!t.push&&e.call(t)===i)}),v={},s=function o(l,h,c,u){this.sc=v[l]?v[l].sc:[],(v[l]=this).gsClass=null,this.func=c;var p=[];this.check=function(t){for(var e,i,n,s,r=h.length,a=r;-1<--r;)(e=v[h[r]]||new o(h[r],[])).gsClass?(p[r]=e.gsClass,a--):t&&e.sc.push(this);if(0===a&&c)for(n=(i=("com.greensock."+l).split(".")).pop(),s=m(i.join("."))[n]=this.gsClass=c.apply(c,p),u&&(d[n]=s,"function"==typeof define&&define.amd?define((f.GreenSockAMDPath?f.GreenSockAMDPath+"/":"")+l.split(".").join("/"),[],function(){return s}):void 0!==W&&W.exports&&(W.exports=s)),r=0;this.sc.length>r;r++)this.sc[r].check()},this.check(!0)},r=f._gsDefine=function(t,e,i,n){return new s(t,e,i,n)},p=u._class=function(t,e,i){return e=e||function(){},r(t,[],function(){return e},i),e};r.globals=d;var t,a=[0,0,1,1],w=[],y=p("easing.Ease",function(t,e,i,n){this._func=t,this._type=i||0,this._power=n||0,this._params=e?a.concat(e):a},!0),x=y.map={},o=y.register=function(t,e,i,n){for(var s,r,a,o,l=e.split(","),h=l.length,c=(i||"easeIn,easeOut,easeInOut").split(",");-1<--h;)for(r=l[h],s=n?p("easing."+r,null,!0):u.easing[r]||{},a=c.length;-1<--a;)o=c[a],x[r+"."+o]=x[o+r]=s[o]=t.getRatio?t:t[o]||new t};for((t=y.prototype)._calcEnd=!1,t.getRatio=function(t){if(this._func)return this._params[0]=t,this._func.apply(null,this._params);var e=this._type,i=this._power,n=1===e?1-t:2===e?t:t<.5?2*t:2*(1-t);return 1===i?n*=n:2===i?n*=n*n:3===i?n*=n*n*n:4===i&&(n*=n*n*n*n),1===e?1-n:2===e?n:t<.5?n/2:1-n/2},O=(h=["Linear","Quad","Cubic","Quart","Quint,Strong"]).length;-1<--O;)t=h[O]+",Power"+O,o(new y(null,null,1,O),t,"easeOut",!0),o(new y(null,null,2,O),t,"easeIn"+(0===O?",easeNone":"")),o(new y(null,null,3,O),t,"easeInOut");x.linear=u.easing.Linear.easeIn,x.swing=u.easing.Quad.easeInOut;var b=p("events.EventDispatcher",function(t){this._listeners={},this._eventTarget=t||this});(t=b.prototype).addEventListener=function(t,e,i,n,s){s=s||0;var r,a,o=this._listeners[t],l=0;for(null==o&&(this._listeners[t]=o=[]),a=o.length;-1<--a;)(r=o[a]).c===e&&r.s===i?o.splice(a,1):0===l&&s>r.pr&&(l=a+1);o.splice(l,0,{c:e,s:i,up:n,pr:s}),this!==A||_||A.wake()},t.removeEventListener=function(t,e){var i,n=this._listeners[t];if(n)for(i=n.length;-1<--i;)if(n[i].c===e)return void n.splice(i,1)},t.dispatchEvent=function(t){var e,i,n,s=this._listeners[t];if(s)for(e=s.length,i=this._eventTarget;-1<--e;)(n=s[e]).up?n.c.call(n.s||i,{type:t,target:i}):n.c.call(n.s||i)};for(var h,T=f.requestAnimationFrame,k=f.cancelAnimationFrame,P=Date.now||function(){return(new Date).getTime()},S=P(),O=(h=["ms","moz","webkit","o"]).length;-1<--O&&!T;)T=f[h[O]+"RequestAnimationFrame"],k=f[h[O]+"CancelAnimationFrame"]||f[h[O]+"CancelRequestAnimationFrame"];p("Ticker",function(t,e){function s(t){var e,i,n=P()-S;p=i&&i+this.totalDuration()/this._timeScale>t},t._enabled=function(t,e){return _||A.wake(),this._gc=!t,this._active=this.isActive(),!0!==e&&(t&&!this.timeline?this._timeline.add(this,this._startTime-this._delay):!t&&this.timeline&&this._timeline._remove(this,!0)),!1},t._kill=function(){return this._enabled(!1,!1)},t.kill=function(t,e){return this._kill(t,e),this},t._uncache=function(t){for(var e=t?this:this.timeline;e;)e._dirty=!0,e=e.timeline;return this},t._swapSelfInParams=function(t){for(var e=t.length,i=t.concat();-1<--e;)"{self}"===t[e]&&(i[e]=this);return i},t.eventCallback=function(t,e,i,n){if("on"===(t||"").substr(0,2)){var s=this.vars;if(1===arguments.length)return s[t];null==e?delete s[t]:(s[t]=e,s[t+"Params"]=c(i)&&-1!==i.join("").indexOf("{self}")?this._swapSelfInParams(i):i,s[t+"Scope"]=n),"onUpdate"===t&&(this._onUpdate=e)}return this},t.delay=function(t){return arguments.length?(this._timeline.smoothChildTiming&&this.startTime(this._startTime+t-this._delay),this._delay=t,this):this._delay},t.duration=function(t){return arguments.length?(this._duration=this._totalDuration=t,this._uncache(!0),this._timeline.smoothChildTiming&&0this._duration?this._duration:t,e)):this._time},t.totalTime=function(t,e,i){if(_||A.wake(),!arguments.length)return this._totalTime;if(this._timeline){if(t<0&&!i&&(t+=this.totalDuration()),this._timeline.smoothChildTiming){this._dirty&&this.totalDuration();var n=this._totalDuration,s=this._timeline;if(nn;)i=i._prev;return i?(t._next=i._next,i._next=t):(t._next=this._first,this._first=t),t._next?t._next._prev=t:this._last=t,t._prev=i,this._timeline&&this._uncache(!0),this},t._remove=function(t,e){return t.timeline===this&&(e||t._enabled(!1,!0),t.timeline=null,t._prev?t._prev._next=t._next:this._first===t&&(this._first=t._next),t._next?t._next._prev=t._prev:this._last===t&&(this._last=t._prev),this._timeline&&this._uncache(!0)),this},t.render=function(t,e,i){var n,s=this._first;for(this._totalTime=this._time=this._rawPrevTime=t;s;)n=s._next,(s._active||t>=s._startTime&&!s._paused)&&(s._reversed?s.render((s._dirty?s.totalDuration():s._totalDuration)-(t-s._startTime)*s._timeScale,e,i):s.render((t-s._startTime)*s._timeScale,e,i)),s=n},t.rawTime=function(){return _||A.wake(),this._totalTime};var R=p("TweenLite",function(t,e,i){if(C.call(this,e,i),this.render=R.prototype.render,null==t)throw"Cannot tween a null target.";this.target=t="string"==typeof t&&R.selector(t)||t;var n,s,r,a=t.jquery||t.length&&t!==f&&t[0]&&(t[0]===f||t[0].nodeType&&t[0].style&&!t.nodeType),o=this.vars.overwrite;if(this._overwrite=o=null==o?Y[R.defaultOverwrite]:"number"==typeof o?o>>0:Y[o],(a||t instanceof Array||t.push&&c(t))&&"number"!=typeof t[0])for(this._targets=r=l.call(t,0),this._propLookup=[],this._siblings=[],n=0;r.length>n;n++)(s=r[n])?"string"!=typeof s?s.length&&s!==f&&s[0]&&(s[0]===f||s[0].nodeType&&s[0].style&&!s.nodeType)?(r.splice(n--,1),this._targets=r=r.concat(l.call(s,0))):(this._siblings[n]=B(s,this,!1),1===o&&1=a._startTime&&a._startTime+a.totalDuration()/a._timeScale>h&&((p||!a._initted)&&h-a._startTime<=2e-10||(c[u++]=a)));for(f=u;-1<--f;)a=c[f],2===n&&a._kill(i,t)&&(r=!0),(2!==n||!a._firstPT&&a._initted)&&a._enabled(!1,!1)&&(r=!0);return r},H=function(t,e,i){for(var n=t._timeline,s=n._timeScale,r=t._startTime;n._timeline;){if(r+=n._startTime,s*=n._timeScale,n._paused)return-100;n=n._timeline}return e<(r/=s)?r-e:i&&r===e||!t._initted&&r-e<2*g?g:(r+=t.totalDuration()/t._timeScale/s)>e+g?0:r-e-g};t._init=function(){var t,e,i,n,s,r=this.vars,a=this._overwrittenProps,o=this._duration,l=!!r.immediateRender,h=r.ease;if(r.startAt){for(n in this._startAt&&(this._startAt.render(-1,!0),this._startAt.kill()),s={},r.startAt)s[n]=r.startAt[n];if(s.overwrite=!1,s.immediateRender=!0,s.lazy=l&&!1!==r.lazy,s.startAt=s.delay=null,this._startAt=R.to(this.target,0,s),l)if(0o.pr;)n=n._next;(o._prev=n?n._prev:r)?o._prev._next=o:s=o,(o._next=n)?n._prev=o:r=o,o=a}o=e._firstPT=s}for(;o;)o.pg&&"function"==typeof o.t[t]&&o.t[t]()&&(i=!0),o=o._next;return i},V.activate=function(t){for(var e=t.length;-1<--e;)t[e].API===V.API&&(X[(new t[e])._propName]=t[e]);return!0},r.plugin=function(t){if(!(t&&t.propName&&t.init&&t.API))throw"illegal plugin definition.";var e,i=t.propName,n=t.priority||0,s=t.overwriteProps,r={init:"_onInitTween",set:"setRatio",kill:"_kill",round:"_roundProps",initAll:"_onInitAllProps"},a=p("plugins."+i.charAt(0).toUpperCase()+i.substr(1)+"Plugin",function(){V.call(this,i,n),this._overwriteProps=s||[]},!0===t.global),o=a.prototype=new V(i);for(e in(o.constructor=a).API=t.API,r)"function"==typeof t[e]&&(o[r[e]]=t[e]);return a.version=t.version,V.activate([a]),a},h=f._gsQueue){for(O=0;h.length>O;O++)h[O]();for(t in v)v[t].func||f.console.log("GSAP encountered missing dependency: com.greensock."+t)}_=!1}}(window)},{}],11:[function(t,e,i){"use strict";(window._gsQueue||(window._gsQueue=[])).push(function(){window._gsDefine("easing.Back",["easing.Ease"],function(m){function t(t,e){var i=c("easing."+t,function(){},!0),n=i.prototype=new m;return n.constructor=i,n.getRatio=e,i}function e(t,e,i,n){var s=c("easing."+t,{easeOut:new e,easeIn:new i,easeInOut:new n},!0);return u(s,t),s}function g(t,e,i){this.t=t,this.v=e,i&&(((this.next=i).prev=this).c=i.v-e,this.gap=i.t-t)}function i(t,e){var i=c("easing."+t,function(t){this._p1=t||0===t?t:1.70158,this._p2=1.525*this._p1},!0),n=i.prototype=new m;return n.constructor=i,n.getRatio=e,n.config=function(t){return new i(t)},i}var n,s,r,a=window.GreenSockGlobals||window,o=a.com.greensock,l=2*Math.PI,h=Math.PI/2,c=o._class,u=m.register||function(){},p=e("Back",i("BackOut",function(t){return--t*t*((this._p1+1)*t+this._p1)+1}),i("BackIn",function(t){return t*t*((this._p1+1)*t-this._p1)}),i("BackInOut",function(t){return(t*=2)<1?.5*t*t*((this._p2+1)*t-this._p2):.5*((t-=2)*t*((this._p2+1)*t+this._p2)+2)})),f=c("easing.SlowMo",function(t,e,i){e=e||0===e?e:.7,null==t?t=.7:1t?this._calcEnd?1-(t=1-t/this._p1)*t:e-(t=1-t/this._p1)*t*t*t*e:t>this._p3?this._calcEnd?1-(t=(t-this._p3)/this._p1)*t:e+(t-e)*(t=(t-this._p3)/this._p1)*t*t*t:this._calcEnd?1:e},f.ease=new f(.7,.7),d.config=f.config=function(t,e,i){return new f(t,e,i)},(d=(n=c("easing.SteppedEase",function(t){t=t||1,this._p1=1/t,this._p2=t+1},!0)).prototype=new m).constructor=n,d.getRatio=function(t){return t<0?t=0:1<=t&&(t=.999999999),(this._p2*t>>0)*this._p1},d.config=n.config=function(t){return new n(t)},(d=(s=c("easing.RoughEase",function(t){for(var e,i,n,s,r,a,o=(t=t||{}).taper||"none",l=[],h=0,c=0|(t.points||20),u=c,p=!1!==t.randomize,f=!0===t.clamp,d=t.template instanceof m?t.template:null,_="number"==typeof t.strength?.4*t.strength:.4;-1<--u;)e=p?Math.random():1/c*u,i=d?d.getRatio(e):e,n="none"===o?_:"out"===o?(s=1-e)*s*_:"in"===o?e*e*_:.5*(s=e<.5?2*e:2*(1-e))*s*_,p?i+=Math.random()*n-.5*n:u%2?i+=.5*n:i-=.5*n,f&&(1e.t){for(;e.next&&t>=e.t;)e=e.next;e=e.prev}else for(;e.prev&&e.t>=t;)e=e.prev;return(this._prev=e).v+(t-e.t)/e.gap*e.c},d.config=function(t){return new s(t)},s.ease=new s,e("Bounce",t("BounceOut",function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375}),t("BounceIn",function(t){return 1/2.75>(t=1-t)?1-7.5625*t*t:t<2/2.75?1-(7.5625*(t-=1.5/2.75)*t+.75):t<2.5/2.75?1-(7.5625*(t-=2.25/2.75)*t+.9375):1-(7.5625*(t-=2.625/2.75)*t+.984375)}),t("BounceInOut",function(t){var e=t<.5;return t=(t=e?1-2*t:2*t-1)<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375,e?.5*(1-t):.5*t+.5})),e("Circ",t("CircOut",function(t){return Math.sqrt(1- --t*t)}),t("CircIn",function(t){return-(Math.sqrt(1-t*t)-1)}),t("CircInOut",function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)})),e("Elastic",(r=function(t,e,i){var n=c("easing."+t,function(t,e){this._p1=t||1,this._p2=e||i,this._p3=this._p2/l*(Math.asin(1/this._p1)||0)},!0),s=n.prototype=new m;return s.constructor=n,s.getRatio=e,s.config=function(t,e){return new n(t,e)},n})("ElasticOut",function(t){return this._p1*Math.pow(2,-10*t)*Math.sin((t-this._p3)*l/this._p2)+1},.3),r("ElasticIn",function(t){return-(this._p1*Math.pow(2,10*--t)*Math.sin((t-this._p3)*l/this._p2))},.3),r("ElasticInOut",function(t){return(t*=2)<1?-.5*this._p1*Math.pow(2,10*--t)*Math.sin((t-this._p3)*l/this._p2):.5*this._p1*Math.pow(2,-10*--t)*Math.sin((t-this._p3)*l/this._p2)+1},.45)),e("Expo",t("ExpoOut",function(t){return 1-Math.pow(2,-10*t)}),t("ExpoIn",function(t){return Math.pow(2,10*(t-1))-.001}),t("ExpoInOut",function(t){return(t*=2)<1?.5*Math.pow(2,10*(t-1)):.5*(2-Math.pow(2,-10*(t-1)))})),e("Sine",t("SineOut",function(t){return Math.sin(t*h)}),t("SineIn",function(t){return 1-Math.cos(t*h)}),t("SineInOut",function(t){return-.5*(Math.cos(Math.PI*t)-1)})),c("easing.EaseLookup",{find:function(t){return m.map[t]}},!0),u(a.SlowMo,"SlowMo","ease,"),u(s,"RoughEase","ease,"),u(n,"SteppedEase","ease,"),p},!0)}),window._gsDefine&&window._gsQueue.pop()()},{}],12:[function(t,e,i){"use strict";var Lt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};(window._gsQueue||(window._gsQueue=[])).push(function(){window._gsDefine("plugins.CSSPlugin",["plugins.TweenPlugin","TweenLite"],function(r,p){function J(){r.call(this,"css"),this._overwriteProps.length=0,this.setRatio=J.prototype.setRatio}var d,T,k,f,_={},t=J.prototype=new r("css");(t.constructor=J).version="1.12.1",J.API=2,J.defaultTransformPerspective=0,J.defaultSkewType="compensated",J.suffixMap={top:t="px",right:t,bottom:t,left:t,width:t,height:t,fontSize:t,padding:t,margin:t,perspective:t,lineHeight:""};function a(t,e){return e.toUpperCase()}function o(t){return E.test("string"==typeof t?t:(t.currentStyle?t.currentStyle.filter:t.style.filter)||"")?parseFloat(RegExp.$1)/100:1}function m(t){window.console&&console.log(t)}function P(t,e){var i,n,s=(e=e||V).style;if(void 0!==s[t])return t;for(t=t.charAt(0).toUpperCase()+t.substr(1),i=["O","Moz","ms","Ms","Webkit"],n=5;-1<--n&&void 0===s[i[n]+t];);return 0<=n?(G="-"+(K=3===n?"ms":i[n]).toLowerCase()+"-",K+t):null}function g(t,e){var i,n,s={};if(e=e||nt(t,null))if(i=e.length)for(;-1<--i;)s[e[i].replace(Y,a)]=e.getPropertyValue(e[i]);else for(i in e)s[i]=e[i];else if(e=t.currentStyle||t.style)for(i in e)"string"==typeof i&&void 0===s[i]&&(s[i.replace(Y,a)]=e[i]);return Z||(s.opacity=o(t)),n=St(t,e,!1),s.rotation=n.rotation,s.skewX=n.skewX,s.scaleX=n.scaleX,s.scaleY=n.scaleY,s.x=n.x,s.y=n.y,kt&&(s.z=n.z,s.rotationX=n.rotationX,s.rotationY=n.rotationY,s.scaleZ=n.scaleZ),s.filters&&delete s.filters,s}function v(t,e,i,n,s){var r,a,o,l={},h=t.style;for(a in i)"cssText"!==a&&"length"!==a&&isNaN(a)&&(e[a]!==(r=i[a])||s&&s[a])&&-1===a.indexOf("Origin")&&("number"==typeof r||"string"==typeof r)&&(l[a]="auto"!==r||"left"!==a&&"top"!==a?""!==r&&"auto"!==r&&"none"!==r||"string"!=typeof e[a]||""===e[a].replace(c,"")?r:0:at(t,a),void 0!==h[a]&&(o=new ft(h,a,h[a],o)));if(n)for(a in n)"className"!==a&&(l[a]=n[a]);return{difs:l,firstMPT:o}}function w(t,e){null!=t&&""!==t&&"auto"!==t&&"auto auto"!==t||(t="0 0");var i=t.split(" "),n=-1!==t.indexOf("left")?"0%":-1!==t.indexOf("right")?"100%":i[0],s=-1!==t.indexOf("top")?"0%":-1!==t.indexOf("bottom")?"100%":i[1];return null==s?s="0":"center"===s&&(s="50%"),("center"===n||isNaN(parseFloat(n))&&-1===(n+"").indexOf("="))&&(n="50%"),e&&(e.oxp=-1!==n.indexOf("%"),e.oyp=-1!==s.indexOf("%"),e.oxr="="===n.charAt(1),e.oyr="="===s.charAt(1),e.ox=parseFloat(n.replace(c,"")),e.oy=parseFloat(s.replace(c,""))),n+" "+s+(2>16,255&t>>8,255&t]:(","===t.charAt(t.length-1)&&(t=t.substr(0,t.length-1)),ht[t]?ht[t]:"#"===t.charAt(0)?(4===t.length&&(t="#"+(e=t.charAt(1))+e+(i=t.charAt(2))+i+(n=t.charAt(3))+n),[(t=parseInt(t.substr(1),16))>>16,255&t>>8,255&t]):("hsl"===t.substr(0,3)?(t=t.match(I),s=Number(t[0])%360/360,r=Number(t[1])/100,e=2*(a=Number(t[2])/100)-(i=a<=.5?a*(1+r):a+r-a*r),3a ",!!(e=n.getElementsByTagName("a")[0])&&/^0.55/.test(e.style.opacity)),G="",K="",nt=H.defaultView?H.defaultView.getComputedStyle:function(){},st=J.getStyle=function(t,e,i,n,s){var r;return Z||"opacity"!==e?(!n&&t.style[e]?r=t.style[e]:(i=i||nt(t))?r=i[e]||i.getPropertyValue(e)||i.getPropertyValue(e.replace(u,"-$1").toLowerCase()):t.currentStyle&&(r=t.currentStyle[e]),null==s||r&&"none"!==r&&"auto"!==r&&"auto auto"!==r?r:s):o(t)},rt=s.convertToPixels=function(t,e,i,n,s){if("px"===n||!n)return i;if("auto"===n||!i)return 0;var r,a,o,l=$.test(e),h=t,c=V.style,u=i<0;if(u&&(i=-i),"%"===n&&-1!==e.indexOf("border"))r=i/100*(l?t.clientWidth:t.clientHeight);else{if(c.cssText="border:0 solid red;position:"+st(t,"position")+";line-height:0;","%"!==n&&h.appendChild)c[l?"borderLeftWidth":"borderTopWidth"]=i+n;else{if(a=(h=t.parentNode||H.body)._gsCache,o=p.ticker.frame,a&&l&&a.time===o)return a.width*i/100;c[l?"width":"height"]=i+n}h.appendChild(V),r=parseFloat(V[l?"offsetWidth":"offsetHeight"]),h.removeChild(V),l&&"%"===n&&!1!==J.cacheWidths&&((a=h._gsCache=h._gsCache||{}).time=o,a.width=r/i*100),0!==r||s||(r=rt(t,e,i,n,!0))}return u?-r:r},at=s.calculateOffset=function(t,e,i){if("absolute"!==st(t,"position",i))return 0;var n="left"===e?"Left":"Top",s=st(t,"margin"+n,i);return t["offset"+n]-(rt(t,e,parseFloat(s),s.replace(X,""))||0)},ot={width:["Left","Right"],height:["Top","Bottom"]},lt=["marginLeft","marginRight","marginTop","marginBottom"],ht={aqua:[0,255,255],lime:[0,255,0],silver:[192,192,192],black:[0,0,0],maroon:[128,0,0],teal:[0,128,128],blue:[0,0,255],navy:[0,0,128],white:[255,255,255],fuchsia:[255,0,255],olive:[128,128,0],yellow:[255,255,0],orange:[255,165,0],gray:[128,128,128],purple:[128,0,128],green:[0,128,0],red:[255,0,0],pink:[255,192,203],cyan:[0,255,255],transparent:[255,255,255,0]},ct="(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#.+?\\b";for(t in ht)ct+="|"+t+"\\b";ct=RegExp(ct+")","gi");function ut(t,e,r,a){if(null==t)return function(t){return t};var o,l=e?(t.match(ct)||[""])[0]:"",h=t.split(l).join("").match(L)||[],c=t.substr(0,t.indexOf(h[0])),u=")"===t.charAt(t.length-1)?")":"",p=-1!==t.indexOf(" ")?" ":",",f=h.length,d=0n;n++)s[n]=o(s[n]);return s.join(",")}if(e=(t.match(ct)||[l])[0],n=(i=t.split(e).join("").match(L)||[]).length,f>n--)for(;f>++n;)i[n]=r?i[0|(n-1)/2]:h[n];return c+i.join(p)+p+e+u+(-1!==t.indexOf("inset")?" inset":"")}:function(t){var e,i,n;if("number"==typeof t)t+=d;else if(a&&B.test(t)){for(i=t.replace(B,"|").split("|"),n=0;i.length>n;n++)i[n]=o(i[n]);return i.join(",")}if(n=(e=t.match(L)||[]).length,f>n--)for(;f>++n;)e[n]=r?e[0|(n-1)/2]:h[n];return c+e.join(p)+u}:function(t){return t}}function pt(h){return h=h.split(","),function(t,e,i,n,s,r,a){var o,l=(e+"").split(" ");for(a={},o=0;o<4;o++)a[h[o]]=l[o]=l[o]||l[(o-1)/2>>0];return n.parse(t,a,s,r)}}var ft=(s._setPluginRatio=function(t){this.plugin.setRatio(t);for(var e,i,n,s,r=this.data,a=r.proxy,o=r.firstMPT;o;)e=a[o.v],o.r?e=Math.round(e):e<1e-6&&-1e-6n;n++)s+=i["xn"+n]+i["xs"+(n+1)];i.e=s}}else i.e=i.s+i.xs0;o=o._next}},function(t,e,i,n,s){this.t=t,this.p=e,this.v=i,this.r=s,n&&((n._prev=this)._next=n)}),dt=(s._parseToProxy=function(t,e,i,n,s,r){var a,o,l,h,c,u=n,p={},f={},d=i._transform,_=U;for(i._transform=null,U=e,n=c=i.parse(t,e,n,s),U=_,r&&(i._transform=d,u&&(u._prev=null,u._prev&&(u._prev._next=null)));n&&n!==u;){if(n.type<=1&&(f[o=n.p]=n.s+n.c,p[o]=n.s,r||(h=new ft(n,"s",o,h,n.r),n.c=0),1===n.type))for(a=n.l;0<--a;)l="xn"+a,f[o=n.p+"_"+l]=n.data[l],p[o]=n[l],r||(h=new ft(n,l,o,h,n.rxp[l]));n=n._next}return{proxy:p,end:f,firstMPT:h,pt:c}},s.CSSPropTween=function(t,e,i,n,s,r,a,o,l,h,c){this.t=t,this.p=e,this.s=i,this.c=n,this.n=a||e,t instanceof dt||f.push(this.n),this.r=o,this.type=r||0,l&&(this.pr=l,d=!0),this.b=void 0===h?i:h,this.e=void 0===c?i+n:c,s&&((this._next=s)._prev=this)}),_t=J.parseComplex=function(t,e,i,n,s,r,a,o,l,h){a=new dt(t,e,0,0,a,h?2:1,null,!1,o,i=i||r||"",n),n+="";var c,u,p,f,d,_,m,g,v,w,y,x,b=i.split(", ").join(",").split(" "),T=n.split(", ").join(",").split(" "),k=b.length,P=!1!==C;for(-1===n.indexOf(",")&&-1===i.indexOf(",")||(b=b.join(" ").replace(B,", ").split(" "),T=T.join(" ").replace(B,", ").split(" "),k=b.length),k!==T.length&&(k=(b=(r||"").split(" ")).length),a.plugin=l,a.setRatio=h,c=0;cu;u++)y=_[u],w=f.indexOf(y,p),a.appendXtra(f.substr(p,w-p),Number(y),S(m[u],y),"",P&&"px"===f.substr(w+y.length,2),0===u),p=w+y.length;a["xs"+a.l]+=f.substr(p)}else a["xs"+a.l]+=a.l?" "+f:f;if(-1!==n.indexOf("=")&&a.data){for(x=a.xs0+a.data.s,c=1;a.l>c;c++)x+=a["xs"+c]+a.data["xn"+c];a.e=x+a["xs"+c]}return a.l||(a.type=-1,a.xs0=a.e),a.xfirst||a},mt=9;for((t=dt.prototype).l=t.pr=0;0<--mt;)t["xn"+mt]=0,t["xs"+mt]="";t.xs0="",t._next=t._prev=t.xfirst=t.data=t.plugin=t.setRatio=t.rxp=null,t.appendXtra=function(t,e,i,n,s,r){var a=this,o=a.l;return a["xs"+o]+=r&&o?" "+t:t||"",i||0===o||a.plugin?(a.l++,a.type=a.setRatio?2:1,a["xs"+a.l]=n||"",0n;n++)e.prefix=0===n&&e.prefix,e.defaultValue=i[n]||r,new gt(s[n],e)};(t=gt.prototype).parseComplex=function(t,e,i,n,s,r){var a,o,l,h,c,u=this.keyword;if(this.multi&&(B.test(i)||B.test(e)?(o=e.replace(B,"|").split("|"),l=i.replace(B,"|").split("|")):u&&(o=[e],l=[i])),l){for(h=l.length>o.length?l.length:o.length,a=0;aH[a]&&H[a]>-W&&(H[a]=0);return i&&(t._gsTransform=H),H},Ot=s.set3DTransformRatio=function(t){var e,i,n,s,r,a,o,l,h,c,u,p,f,d,_,m,g,v,w,y,x,b,T,k=this.data,P=this.t.style,S=k.rotation*et,O=k.scaleX,C=k.scaleY,A=k.scaleZ,M=k.perspective;if(1!==t&&0!==t||"auto"!==k.force3D||k.rotationY||k.rotationX||1!==A||M||k.z){if(R&&(O<1e-4&&-1e-4b;b++)this.p.indexOf("border")&&(g[b]=P(g[b])),-1!==(o=a=st(t,g[b],k,!1,"0px")).indexOf(" ")&&(o=(a=o.split(" "))[0],a=a[1]),l=r=x[b],h=parseFloat(o),p=o.substr((h+"").length),""===(u=(f="="===l.charAt(1))?(c=parseInt(l.charAt(0)+"1",10),l=l.substr(2),c*=parseFloat(l),l.substr((c+"").length-(c<0?1:0))||""):(c=parseFloat(l),l.substr((c+"").length)))&&(u=T[i]||p),u!==p&&(d=rt(t,"borderLeft",h,p),_=rt(t,"borderTop",h,p),a="%"===u?(o=d/w*100+"%",_/y*100+"%"):"em"===u?(o=d/(m=rt(t,"borderLeft",1,"em"))+"em",_/m+"em"):(o=d+"px",_+"px"),f&&(l=parseFloat(o)+c+u,r=parseFloat(a)+c+u)),s=_t(v,g[b],o+" "+a,l+" "+r,!1,"0px",s);return s},prefix:!0,formatter:ut("0px 0px 0px 0px",!1,!0)}),vt("backgroundPosition",{defaultValue:"0 0",parser:function(t,e,i,n,s,r){var a,o,l,h,c,u,p="background-position",f=k||nt(t,null),d=this.format((f?D?f.getPropertyValue(p+"-x")+" "+f.getPropertyValue(p+"-y"):f.getPropertyValue(p):t.currentStyle.backgroundPositionX+" "+t.currentStyle.backgroundPositionY)||"0 0"),_=this.format(e);if(-1!==d.indexOf("%")!=(-1!==_.indexOf("%"))&&((u=st(t,"backgroundImage").replace(F,""))&&"none"!==u)){for(a=d.split(" "),o=_.split(" "),W.setAttribute("src",u),l=2;-1<--l;)(h=-1!==(d=a[l]).indexOf("%"))!=(-1!==o[l].indexOf("%"))&&(c=0===l?t.offsetWidth-W.width:t.offsetHeight-W.height,a[l]=h?parseFloat(d)/100*c+"px":parseFloat(d)/c*100+"%");d=a.join(" ")}return this.parseComplex(t.style,d,_,s,r)},formatter:w}),vt("backgroundSize",{defaultValue:"0 0",formatter:w}),vt("perspective",{defaultValue:"0px",prefix:!0}),vt("perspectiveOrigin",{defaultValue:"50% 50%",prefix:!0}),vt("transformStyle",{prefix:!0}),vt("backfaceVisibility",{prefix:!0}),vt("userSelect",{prefix:!0}),vt("margin",{parser:pt("marginTop,marginRight,marginBottom,marginLeft")}),vt("padding",{parser:pt("paddingTop,paddingRight,paddingBottom,paddingLeft")}),vt("clip",{defaultValue:"rect(0px,0px,0px,0px)",parser:function(t,e,i,n,s,r){var a,o,l;return e=D<9?(o=t.currentStyle,l=D<8?" ":",",a="rect("+o.clipTop+l+o.clipRight+l+o.clipBottom+l+o.clipLeft+")",this.format(e).split(",").join(l)):(a=this.format(st(t,this.p,k,!1,this.dflt)),this.format(e)),this.parseComplex(t.style,a,e,s,r)}}),vt("textShadow",{defaultValue:"0px 0px 0px #999",color:!0,multi:!0}),vt("autoRound,strictUnits",{parser:function(t,e,i,n,s){return s}}),vt("border",{defaultValue:"0px solid #000",parser:function(t,e,i,n,s,r){return this.parseComplex(t.style,this.format(st(t,"borderTopWidth",k,!1,"0px")+" "+st(t,"borderTopStyle",k,!1,"solid")+" "+st(t,"borderTopColor",k,!1,"#000")),this.format(e),s,r)},color:!0,formatter:function(t){var e=t.split(" ");return e[0]+" "+(e[1]||"solid")+" "+(t.match(ct)||["#000"])[0]}}),vt("borderWidth",{parser:pt("borderTopWidth,borderRightWidth,borderBottomWidth,borderLeftWidth")}),vt("float,cssFloat,styleFloat",{parser:function(t,e,i,n,s){var r=t.style,a="cssFloat"in r?"cssFloat":"styleFloat";return new dt(r,a,0,0,s,-1,i,!1,0,r[a],e)}});function At(t){var e,i=this.t,n=i.filter||st(this.data,"filter"),s=0|this.s+this.c*t;100==s&&(e=-1===n.indexOf("atrix(")&&-1===n.indexOf("radient(")&&-1===n.indexOf("oader(")?(i.removeAttribute("filter"),!st(this.data,"filter")):(i.filter=n.replace(h,""),!0)),e||(this.xn1&&(i.filter=n=n||"alpha(opacity="+s+")"),-1===n.indexOf("pacity")?0==s&&this.xn1||(i.filter=n+" alpha(opacity="+s+")"):i.filter=n.replace(E,"opacity="+s))}vt("opacity,alpha,autoAlpha",{defaultValue:"1",parser:function(t,e,i,n,s,r){var a=parseFloat(st(t,"opacity",k,!1,"1")),o=t.style,l="autoAlpha"===i;return"string"==typeof e&&"="===e.charAt(1)&&(e=("-"===e.charAt(0)?-1:1)*parseFloat(e.substr(2))+a),l&&1===a&&"hidden"===st(t,"visibility",k)&&0!==e&&(a=0),Z?s=new dt(o,"opacity",a,e-a,s):((s=new dt(o,"opacity",100*a,100*(e-a),s)).xn1=l?1:0,o.zoom=1,s.type=2,s.b="alpha(opacity="+s.s+")",s.e="alpha(opacity="+(s.s+s.c)+")",s.data=t,s.plugin=r,s.setRatio=At),l&&((s=new dt(o,"visibility",0,0,s,-1,null,!1,0,0!==a?"inherit":"hidden",0===e?"hidden":"inherit")).xs0="inherit",n._overwriteProps.push(s.n),n._overwriteProps.push(i)),s}});function Mt(t,e){e&&(t.removeProperty?("ms"===e.substr(0,2)&&(e="M"+e.substr(1)),t.removeProperty(e.replace(u,"-$1").toLowerCase())):t.removeAttribute(e))}function Rt(t){if(this.t._gsClassPT=this,1===t||0===t){this.t.setAttribute("class",0===t?this.b:this.e);for(var e=this.data,i=this.t.style;e;)e.v?i[e.p]=e.v:Mt(i,e.p),e=e._next;1===t&&this.t._gsClassPT===this&&(this.t._gsClassPT=null)}else this.t.getAttribute("class")!==this.e&&this.t.setAttribute("class",this.e)}vt("className",{parser:function(t,e,i,n,s,r,a){var o,l,h,c,u,p=t.getAttribute("class")||"",f=t.style.cssText;if((s=n._classNamePT=new dt(t,i,0,0,s,2)).setRatio=Rt,s.pr=-11,d=!0,s.b=p,l=g(t,k),h=t._gsClassPT){for(c={},u=h.data;u;)c[u.p]=1,u=u._next;h.setRatio(1)}return(t._gsClassPT=s).e="="!==e.charAt(1)?e:p.replace(RegExp("\\s*\\b"+e.substr(2)+"\\b"),"")+("+"===e.charAt(0)?" "+e.substr(2):""),n._tween._duration&&(t.setAttribute("class",s.e),o=v(t,l,g(t),a,c),t.setAttribute("class",p),s.data=o.firstMPT,t.style.cssText=f,s=s.xfirst=n.parse(t,o.difs,s,r)),s}});function Dt(t){if((1===t||0===t)&&this.data._totalTime===this.data._totalDuration&&"isFromStart"!==this.data.data){var e,i,n,s,r=this.t.style,a=_.transform.parse;if("all"===this.e)s=!(r.cssText="");else for(n=(e=this.e.split(",")).length;-1<--n;)i=e[n],_[i]&&(_[i].parse===a?s=!0:i="transformOrigin"===i?Tt:_[i].p),Mt(r,i);s&&(Mt(r,xt),this.t._gsTransform&&delete this.t._gsTransform)}}for(vt("clearProps",{parser:function(t,e,i,n,s){return(s=new dt(t,i,0,0,s,2)).setRatio=Dt,s.e=e,s.pr=-10,s.data=n._tween,d=!0,s}}),t="bezier,throwProps,physicsProps,physics2D".split(","),mt=t.length;mt--;)!function(t){var l;_[t]||(l=t.charAt(0).toUpperCase()+t.substr(1)+"Plugin",vt(t,{parser:function(t,e,i,n,s,r,a){var o=(window.GreenSockGlobals||window).com.greensock.plugins[l];return o?(o._cssRegister(),_[i].parse(t,e,i,n,s,r,a)):(m("Error: "+l+" js file not loaded."),s)}}))}(t[mt]);(t=J.prototype)._firstPT=null,t._onInitTween=function(t,e,i){if(!t.nodeType)return!1;this._target=t,this._tween=i,this._vars=e,C=e.autoRound,d=!1,T=e.suffixMap||J.suffixMap,k=nt(t,""),f=this._overwriteProps;var n,s,r,a,o,l,h,c,u,p=t.style;if(b&&""===p.zIndex&&("auto"!==(n=st(t,"zIndex",k))&&""!==n||this._addLazySet(p,"zIndex",0)),"string"==typeof e&&(a=p.cssText,n=g(t,k),p.cssText=a+";"+e,n=v(t,n,g(t)).difs,!Z&&z.test(e)&&(n.opacity=parseFloat(RegExp.$1)),e=n,p.cssText=a),this._firstPT=s=this.parse(t,e,null),this._transformType){for(u=3===this._transformType,xt?A&&(b=!0,""===p.zIndex&&("auto"!==(h=st(t,"zIndex",k))&&""!==h||this._addLazySet(p,"zIndex",0)),M&&this._addLazySet(p,"WebkitBackfaceVisibility",this._vars.WebkitBackfaceVisibility||(u?"visible":"hidden"))):p.zoom=1,r=s;r&&r._next;)r=r._next;c=new dt(t,"transform",0,0,null,2),this._linkCSSP(c,null,r),c.setRatio=u&&kt?Ot:xt?Ct:wt,c.data=this._transform||St(t,k,!0),f.pop()}if(d){for(;s;){for(l=s._next,r=a;r&&r.pr>s.pr;)r=r._next;(s._prev=r?r._prev:o)?s._prev._next=s:a=s,(s._next=r)?r._prev=s:o=s,s=l}this._firstPT=a}return!0},t.parse=function(t,e,i,n){var s,r,a,o,l,h,c,u,p,f,d=t.style;for(s in e)h=e[s],(r=_[s])?i=r.parse(t,h,s,this,i,n,e):(l=st(t,s,k)+"",p="string"==typeof h,"color"===s||"fill"===s||"stroke"===s||-1!==s.indexOf("Color")||p&&N.test(h)?(p||(h=(3<(h=O(h)).length?"rgba(":"rgb(")+h.join(",")+")"),i=_t(d,s,l,h,!0,"transparent",i,0,n)):!p||-1===h.indexOf(" ")&&-1===h.indexOf(",")?(c=(a=parseFloat(l))||0===a?l.substr((a+"").length):"",""!==l&&"auto"!==l||(c="width"===s||"height"===s?(a=function(t,e,i){var n=parseFloat("width"===e?t.offsetWidth:t.offsetHeight),s=ot[e],r=s.length;for(i=i||nt(t,null);-1<--r;)n-=parseFloat(st(t,"padding"+s[r],i,!0))||0,n-=parseFloat(st(t,"border"+s[r]+"Width",i,!0))||0;return n}(t,s,k),"px"):"left"===s||"top"===s?(a=at(t,s,k),"px"):(a="opacity"!==s?0:1,"")),""===(u=(f=p&&"="===h.charAt(1))?(o=parseInt(h.charAt(0)+"1",10),h=h.substr(2),o*=parseFloat(h),h.replace(X,"")):(o=parseFloat(h),p&&h.substr((o+"").length)||""))&&(u=s in T?T[s]:c),h=o||0===o?(f?o+a:o)+u:e[s],c!==u&&""!==u&&(o||0===o)&&a&&(a=rt(t,s,a,c),"%"===u?(a/=rt(t,s,100,"%")/100,!0!==e.strictUnits&&(l=a+"%")):"em"===u?a/=rt(t,s,1,"em"):"px"!==u&&(o=rt(t,s,o,u),u="px"),f&&(o||0===o)&&(h=o+a+u)),f&&(o+=a),!a&&0!==a||!o&&0!==o?void 0!==d[s]&&(h||"NaN"!=h+""&&null!=h)?(i=new dt(d,s,o||a||0,0,i,-1,s,!1,0,l,h)).xs0="none"!==h||"display"!==s&&-1===s.indexOf("Style")?h:l:m("invalid "+s+" tween value: "+e[s]):(i=new dt(d,s,a,o-a,i,0,s,!1!==C&&("px"===u||"zIndex"===s),0,l,h)).xs0=u):i=_t(d,s,l,h,!0,null,i,0,n)),n&&i&&!i.plugin&&(i.plugin=n);return i},t.setRatio=function(t){var e,i,n,s=this._firstPT;if(1!==t||this._tween._time!==this._tween._duration&&0!==this._tween._time)if(t||this._tween._time!==this._tween._duration&&0!==this._tween._time||-1e-6===this._tween._rawPrevTime)for(;s;){if(e=s.c*t+s.s,s.r?e=Math.round(e):e<1e-6&&-1e-6n;n++)i+=s["xn"+n]+s["xs"+(n+1)];s.t[s.p]=i}else-1===s.type?s.t[s.p]=s.xs0:s.setRatio&&s.setRatio(t);else s.t[s.p]=e+s.xs0;s=s._next}else for(;s;)2!==s.type?s.t[s.p]=s.b:s.setRatio(t),s=s._next;else for(;s;)2!==s.type?s.t[s.p]=s.e:s.setRatio(t),s=s._next},t._enableTransforms=function(t){this._transformType=t||3===this._transformType?3:2,this._transform=this._transform||St(this._target,k,!0)};function It(){this.t[this.p]=this.e,this.data._linkCSSP(this,this._next,null,!0)}t._addLazySet=function(t,e,i){var n=this._firstPT=new dt(t,e,0,0,this._firstPT,2);n.e=i,n.setRatio=It,n.data=this},t._linkCSSP=function(t,e,i,n){return t&&(e&&(e._prev=t),t._next&&(t._next._prev=t._prev),t._prev?t._prev._next=t._next:this._firstPT===t&&(this._firstPT=t._next,n=!0),i?i._next=t:n||null!==this._firstPT||(this._firstPT=t),t._next=e,t._prev=i),t},t._kill=function(t){var e,i,n,s=t;if(t.autoAlpha||t.alpha){for(i in s={},t)s[i]=t[i];s.opacity=1,s.autoAlpha&&(s.visibility=1)}return t.className&&(e=this._classNamePT)&&((n=e.xfirst)&&n._prev?this._linkCSSP(n._prev,e._next,n._prev._prev):n===this._firstPT&&(this._firstPT=e._next),e._next&&this._linkCSSP(e._next,e._next._next,n._prev),this._classNamePT=null),r.prototype._kill.call(this,s)};function jt(t,e,i){var n,s,r,a;if(t.slice)for(s=t.length;-1<--s;)jt(t[s],e,i);else for(s=(n=t.childNodes).length;-1<--s;)a=(r=n[s]).type,r.style&&(e.push(g(r)),i&&i.push(r)),1!==a&&9!==a&&11!==a||!r.childNodes.length||jt(r,e,i)}return J.cascadeTo=function(t,e,i){var n,s,r,a=p.to(t,e,i),o=[a],l=[],h=[],c=[],u=p._internals.reservedProps;for(t=a._targets||a.target,jt(t,l,c),a.render(e,!0),jt(t,h),a.render(0,!0),a._enabled(!0),n=c.length;-1<--n;)if((s=v(c[n],l[n],h[n])).firstMPT){for(r in s=s.difs,i)u[r]&&(s[r]=i[r]);o.push(p.to(c[n],e,s))}return o},r.activate([J]),J},!0)}),window._gsDefine&&window._gsQueue.pop()()},{}],13:[function(t,e,i){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};(window._gsQueue||(window._gsQueue=[])).push(function(){function r(t,e){var i="x"===e?"Width":"Height",n="scroll"+i,s="client"+i,r=document.body;return t===o||t===a||t===r?Math.max(a[n],r[n])-(o["inner"+i]||Math.max(a[s],r[s])):t[n]-t["offset"+i]}var a=document.documentElement,o=window,t=window._gsDefine.plugin({propName:"scrollTo",API:2,version:"1.7.3",init:function(t,e,i){return this._wdw=t===o,this._target=t,this._tween=i,"object"!=(void 0===e?"undefined":n(e))&&(e={y:e}),this._autoKill=!1!==e.autoKill,this.x=this.xPrev=this.getX(),this.y=this.yPrev=this.getY(),null!=e.x?(this._addTween(this,"x",this.x,"max"===e.x?r(t,"x"):e.x,"scrollTo_x",!0),this._overwriteProps.push("scrollTo_x")):this.skipX=!0,null!=e.y?(this._addTween(this,"y",this.y,"max"===e.y?r(t,"y"):e.y,"scrollTo_y",!0),this._overwriteProps.push("scrollTo_y")):this.skipY=!0,!0},set:function(t){this._super.setRatio.call(this,t);var e=this._wdw||!this.skipX?this.getX():this.xPrev,i=this._wdw||!this.skipY?this.getY():this.yPrev,n=i-this.yPrev,s=e-this.xPrev;this._autoKill&&(!this.skipX&&(7e&&(this.skipX=!0),!this.skipY&&(7i&&(this.skipY=!0),this.skipX&&this.skipY&&this._tween.kill()),this._wdw?o.scrollTo(this.skipX?e:this.x,this.skipY?i:this.y):(this.skipY||(this._target.scrollTop=this.y),this.skipX||(this._target.scrollLeft=this.x)),this.xPrev=this.x,this.yPrev=this.y}}),e=t.prototype;t.max=r,e.getX=function(){return this._wdw?null!=o.pageXOffset?o.pageXOffset:null!=a.scrollLeft?a.scrollLeft:document.body.scrollLeft:this._target.scrollLeft},e.getY=function(){return this._wdw?null!=o.pageYOffset?o.pageYOffset:null!=a.scrollTop?a.scrollTop:document.body.scrollTop:this._target.scrollTop},e._kill=function(t){return t.scrollTo_x&&(this.skipX=!0),t.scrollTo_y&&(this.skipY=!0),this._super._kill.call(this,t)}}),window._gsDefine&&window._gsQueue.pop()()},{}]},{},[2]);
+//# sourceMappingURL=wpr-admin.js.map
diff --git a/wp-content/plugins/wp-rocket/assets/js/wpr-admin.js.map b/wp-content/plugins/wp-rocket/assets/js/wpr-admin.js.map
new file mode 100644
index 00000000..2903c335
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/wpr-admin.js.map
@@ -0,0 +1 @@
+{"version":3,"names":[],"mappings":"","sources":["wpr-admin.js"],"sourcesContent":["!function r(a,o,l){function h(e,t){if(!o[e]){if(!a[e]){var i=\"function\"==typeof require&&require;if(!t&&i)return i(e,!0);if(c)return c(e,!0);var n=new Error(\"Cannot find module '\"+e+\"'\");throw n.code=\"MODULE_NOT_FOUND\",n}var s=o[e]={exports:{}};a[e][0].call(s.exports,function(t){return h(a[e][1][t]||t)},s,s.exports,r,a,o,l)}return o[e].exports}for(var c=\"function\"==typeof require&&require,t=0;t form > #wpr-options-submit\"),this.$pages=document.querySelectorAll(\".wpr-Page\"),this.$sidebar=document.querySelector(\".wpr-Sidebar\"),this.$content=document.querySelector(\".wpr-Content\"),this.$tips=document.querySelector(\".wpr-Content-tips\"),this.$links=document.querySelectorAll(\".wpr-body a\"),this.$menuItem=null,this.$page=null,this.pageId=null,this.bodyTop=0,this.buttonText=this.$submitButton.value,i.getBodyTop(),window.onhashchange=function(){i.detectID()},window.location.hash?(this.bodyTop=0,this.detectID()):(e=localStorage.getItem(\"wpr-hash\"),this.bodyTop=0,e?(window.location.hash=e,this.detectID()):(this.$menuItems[0].classList.add(\"isActive\"),localStorage.setItem(\"wpr-hash\",\"dashboard\"),window.location.hash=\"#dashboard\"));for(var n=0;nl;l++)i.startAt&&(i.startAt=d(i.startAt)),h.to(t[l],e,d(i),l*n);return this.add(h,s)},t.staggerFrom=function(t,e,i,n,s,r,a,o){return i.immediateRender=0!=i.immediateRender,i.runBackwards=!0,this.staggerTo(t,e,i,n,s,r,a,o)},t.staggerFromTo=function(t,e,i,n,s,r,a,o,l){return n.startAt=i,n.immediateRender=0!=n.immediateRender&&0!=i.immediateRender,this.staggerTo(t,e,n,s,r,a,o,l)},t.call=function(t,e,i,n){return this.add(p.delayedCall(0,t,e,i),n)},t.set=function(t,e,i){return i=this._parseTimeOrLabel(i,0,!0),null==e.immediateRender&&(e.immediateRender=i===this._time&&!this._paused),this.add(new p(t,0,e),i)},f.exportRoot=function(t,e){null==(t=t||{}).smoothChildTiming&&(t.smoothChildTiming=!0);var i,n,s=new f(t),r=s._timeline;for(null==e&&(e=!0),r._remove(s,!0),s._startTime=0,s._rawPrevTime=s._time=s._totalTime=r._time,i=r._first;i;)n=i._next,e&&i instanceof p&&i.target===i.vars.onComplete||s.add(i,i._startTime-i._delay),i=n;return r.add(s,0),s},t.add=function(t,e,i,n){var s,r,a,o,l,h;if(\"number\"!=typeof e&&(e=this._parseTimeOrLabel(e,0,!0,t)),!(t instanceof c)){if(t instanceof Array||t&&t.push&&g(t)){for(i=i||\"normal\",n=n||0,s=e,r=t.length,a=0;at._startTime;l._timeline;)h&&l._timeline.smoothChildTiming?l.totalTime(l._totalTime,!0):l._gc&&l._enabled(!0,!1),l=l._timeline;return this},t.remove=function(t){if(t instanceof c)return this._remove(t,!1);if(t instanceof Array||t&&t.push&&g(t)){for(var e=t.length;-1<--e;)this.remove(t[e]);return this}return\"string\"==typeof t?this.removeLabel(t):this.kill(null,t)},t._remove=function(t,e){u.prototype._remove.call(this,t,e);var i=this._last;return i?this._time>i._startTime+i._totalDuration/i._timeScale&&(this._time=this.duration(),this._totalTime=this._totalDuration):this._time=this._totalTime=this._duration=this._totalDuration=0,this},t.append=function(t,e){return this.add(t,this._parseTimeOrLabel(null,e,!0,t))},t.insert=t.insertMultiple=function(t,e,i,n){return this.add(t,e||0,i,n)},t.appendMultiple=function(t,e,i,n){return this.add(t,this._parseTimeOrLabel(null,e,!0,t),i,n)},t.addLabel=function(t,e){return this._labels[t]=this._parseTimeOrLabel(e),this},t.addPause=function(t,e,i,n){return this.call(s,[\"{self}\",e,i,n],this,t)},t.removeLabel=function(t){return delete this._labels[t],this},t.getLabelTime=function(t){return null!=this._labels[t]?this._labels[t]:-1},t._parseTimeOrLabel=function(t,e,i,n){var s;if(n instanceof c&&n.timeline===this)this.remove(n);else if(n&&(n instanceof Array||n.push&&g(n)))for(s=n.length;-1<--s;)n[s]instanceof c&&n[s].timeline===this&&this.remove(n[s]);if(\"string\"==typeof e)return this._parseTimeOrLabel(e,i&&\"number\"==typeof t&&null==this._labels[e]?t-this.duration():0,i);if(e=e||0,\"string\"!=typeof t||!isNaN(t)&&null==this._labels[t])null==t&&(t=this.duration());else{if(-1===(s=t.indexOf(\"=\")))return null==this._labels[t]?i?this._labels[t]=this.duration()+e:e:this._labels[t]+e;e=parseInt(t.charAt(s-1)+\"1\",10)*Number(t.substr(s+1)),t=1_&&(a=\"onReverseComplete\"))),this._rawPrevTime=this._duration||!e||t||this._rawPrevTime===t?t:_,t=l+1e-4):t<1e-7?(((this._totalTime=this._time=0)!==h||0===this._duration&&this._rawPrevTime!==_&&(0=h)for(n=this._first;n&&(r=n._next,!this._paused||p);)(n._active||n._startTime<=this._time&&!n._paused&&!n._gc)&&(n._reversed?n.render((n._dirty?n.totalDuration():n._totalDuration)-(t-n._startTime)*n._timeScale,e,i):n.render((t-n._startTime)*n._timeScale,e,i)),n=r;else for(n=this._last;n&&(r=n._prev,!this._paused||p);)(n._active||h>=n._startTime&&!n._paused&&!n._gc)&&(n._reversed?n.render((n._dirty?n.totalDuration():n._totalDuration)-(t-n._startTime)*n._timeScale,e,i):n.render((t-n._startTime)*n._timeScale,e,i)),n=r;this._onUpdate&&(e||this._onUpdate.apply(this.vars.onUpdateScope||this,this.vars.onUpdateParams||v)),a&&(this._gc||c!==this._startTime&&u===this._timeScale||!(0===this._time||l>=this.totalDuration())||(s&&(this._timeline.autoRemoveChildren&&this._enabled(!1,!1),this._active=!1),!e&&this.vars[a]&&this.vars[a].apply(this.vars[a+\"Scope\"]||this,this.vars[a+\"Params\"]||v)))}},t._hasPausedChild=function(){for(var t=this._first;t;){if(t._paused||t instanceof f&&t._hasPausedChild())return!0;t=t._next}return!1},t.getChildren=function(t,e,i,n){n=n||-9999999999;for(var s=[],r=this._first,a=0;r;)n>r._startTime||(r instanceof p?!1!==e&&(s[a++]=r):(!1!==i&&(s[a++]=r),!1!==t&&(a=(s=s.concat(r.getChildren(!0,e,i))).length))),r=r._next;return s},t.getTweensOf=function(t,e){var i,n,s=this._gc,r=[],a=0;for(s&&this._enabled(!0,!0),n=(i=p.getTweensOf(t)).length;-1<--n;)(i[n].timeline===this||e&&this._contains(i[n]))&&(r[a++]=i[n]);return s&&this._enabled(!1,!0),r},t._contains=function(t){for(var e=t.timeline;e;){if(e===this)return!0;e=e.timeline}return!1},t.shiftChildren=function(t,e,i){i=i||0;for(var n,s=this._first,r=this._labels;s;)s._startTime>=i&&(s._startTime+=t),s=s._next;if(e)for(n in r)r[n]>=i&&(r[n]+=t);return this._uncache(!0)},t._kill=function(t,e){if(!t&&!e)return this._enabled(!1,!1);for(var i=e?this.getTweensOf(e):this.getChildren(!0,!0,!1),n=i.length,s=!1;-1<--n;)i[n]._kill(t,e)&&(s=!0);return s},t.clear=function(t){var e=this.getChildren(!1,!0,!0),i=e.length;for(this._time=this._totalTime=0;-1<--i;)e[i]._enabled(!1,!1);return!1!==t&&(this._labels={}),this._uncache(!0)},t.invalidate=function(){for(var t=this._first;t;)t.invalidate(),t=t._next;return this},t._enabled=function(t,e){if(t===this._gc)for(var i=this._first;i;)i._enabled(t,!0),i=i._next;return u.prototype._enabled.call(this,t,e)},t.duration=function(t){return arguments.length?(0!==this.duration()&&0!==t&&this.timeScale(this._duration/t),this):(this._dirty&&this.totalDuration(),this._duration)},t.totalDuration=function(t){if(arguments.length)return 0!==this.totalDuration()&&0!==t&&this.timeScale(this._totalDuration/t),this;if(this._dirty){for(var e,i,n=0,s=this._last,r=999999999999;s;)e=s._prev,s._dirty&&s.totalDuration(),s._startTime>r&&this._sortChildren&&!s._paused?this.add(s,s._startTime-s._delay):r=s._startTime,s._startTime<0&&!s._paused&&(n-=s._startTime,this._timeline.smoothChildTiming&&(this._startTime+=s._startTime/this._timeScale),this.shiftChildren(-s._startTime,!1,-9999999999),r=0),n<(i=s._startTime+s._totalDuration/s._timeScale)&&(n=i),s=e;this._duration=this._totalDuration=n,this._dirty=!1}return this._totalDuration},t.usesFrames=function(){for(var t=this._timeline;t._timeline;)t=t._timeline;return t===c._rootFramesTimeline},t.rawTime=function(){return this._paused?this._totalTime:(this._timeline.rawTime()-this._startTime)*this._timeScale},f},!0)}),window._gsDefine&&window._gsQueue.pop()()},{}],10:[function(t,W,e){\"use strict\";var Q=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t};!function(f){var e,i,d=f.GreenSockGlobals||f;if(!d.TweenLite){var _,m=function(t){for(var e=t.split(\".\"),i=d,n=0;e.length>n;n++)i[e[n]]=i=i[e[n]]||{};return i},u=m(\"com.greensock\"),g=1e-10,l=[].slice,n=function(){},c=(e=Object.prototype.toString,i=e.call([]),function(t){return null!=t&&(t instanceof Array||\"object\"==(void 0===t?\"undefined\":Q(t))&&!!t.push&&e.call(t)===i)}),v={},s=function o(l,h,c,u){this.sc=v[l]?v[l].sc:[],(v[l]=this).gsClass=null,this.func=c;var p=[];this.check=function(t){for(var e,i,n,s,r=h.length,a=r;-1<--r;)(e=v[h[r]]||new o(h[r],[])).gsClass?(p[r]=e.gsClass,a--):t&&e.sc.push(this);if(0===a&&c)for(n=(i=(\"com.greensock.\"+l).split(\".\")).pop(),s=m(i.join(\".\"))[n]=this.gsClass=c.apply(c,p),u&&(d[n]=s,\"function\"==typeof define&&define.amd?define((f.GreenSockAMDPath?f.GreenSockAMDPath+\"/\":\"\")+l.split(\".\").join(\"/\"),[],function(){return s}):void 0!==W&&W.exports&&(W.exports=s)),r=0;this.sc.length>r;r++)this.sc[r].check()},this.check(!0)},r=f._gsDefine=function(t,e,i,n){return new s(t,e,i,n)},p=u._class=function(t,e,i){return e=e||function(){},r(t,[],function(){return e},i),e};r.globals=d;var t,a=[0,0,1,1],w=[],y=p(\"easing.Ease\",function(t,e,i,n){this._func=t,this._type=i||0,this._power=n||0,this._params=e?a.concat(e):a},!0),x=y.map={},o=y.register=function(t,e,i,n){for(var s,r,a,o,l=e.split(\",\"),h=l.length,c=(i||\"easeIn,easeOut,easeInOut\").split(\",\");-1<--h;)for(r=l[h],s=n?p(\"easing.\"+r,null,!0):u.easing[r]||{},a=c.length;-1<--a;)o=c[a],x[r+\".\"+o]=x[o+r]=s[o]=t.getRatio?t:t[o]||new t};for((t=y.prototype)._calcEnd=!1,t.getRatio=function(t){if(this._func)return this._params[0]=t,this._func.apply(null,this._params);var e=this._type,i=this._power,n=1===e?1-t:2===e?t:t<.5?2*t:2*(1-t);return 1===i?n*=n:2===i?n*=n*n:3===i?n*=n*n*n:4===i&&(n*=n*n*n*n),1===e?1-n:2===e?n:t<.5?n/2:1-n/2},O=(h=[\"Linear\",\"Quad\",\"Cubic\",\"Quart\",\"Quint,Strong\"]).length;-1<--O;)t=h[O]+\",Power\"+O,o(new y(null,null,1,O),t,\"easeOut\",!0),o(new y(null,null,2,O),t,\"easeIn\"+(0===O?\",easeNone\":\"\")),o(new y(null,null,3,O),t,\"easeInOut\");x.linear=u.easing.Linear.easeIn,x.swing=u.easing.Quad.easeInOut;var b=p(\"events.EventDispatcher\",function(t){this._listeners={},this._eventTarget=t||this});(t=b.prototype).addEventListener=function(t,e,i,n,s){s=s||0;var r,a,o=this._listeners[t],l=0;for(null==o&&(this._listeners[t]=o=[]),a=o.length;-1<--a;)(r=o[a]).c===e&&r.s===i?o.splice(a,1):0===l&&s>r.pr&&(l=a+1);o.splice(l,0,{c:e,s:i,up:n,pr:s}),this!==A||_||A.wake()},t.removeEventListener=function(t,e){var i,n=this._listeners[t];if(n)for(i=n.length;-1<--i;)if(n[i].c===e)return void n.splice(i,1)},t.dispatchEvent=function(t){var e,i,n,s=this._listeners[t];if(s)for(e=s.length,i=this._eventTarget;-1<--e;)(n=s[e]).up?n.c.call(n.s||i,{type:t,target:i}):n.c.call(n.s||i)};for(var h,T=f.requestAnimationFrame,k=f.cancelAnimationFrame,P=Date.now||function(){return(new Date).getTime()},S=P(),O=(h=[\"ms\",\"moz\",\"webkit\",\"o\"]).length;-1<--O&&!T;)T=f[h[O]+\"RequestAnimationFrame\"],k=f[h[O]+\"CancelAnimationFrame\"]||f[h[O]+\"CancelRequestAnimationFrame\"];p(\"Ticker\",function(t,e){function s(t){var e,i,n=P()-S;p=i&&i+this.totalDuration()/this._timeScale>t},t._enabled=function(t,e){return _||A.wake(),this._gc=!t,this._active=this.isActive(),!0!==e&&(t&&!this.timeline?this._timeline.add(this,this._startTime-this._delay):!t&&this.timeline&&this._timeline._remove(this,!0)),!1},t._kill=function(){return this._enabled(!1,!1)},t.kill=function(t,e){return this._kill(t,e),this},t._uncache=function(t){for(var e=t?this:this.timeline;e;)e._dirty=!0,e=e.timeline;return this},t._swapSelfInParams=function(t){for(var e=t.length,i=t.concat();-1<--e;)\"{self}\"===t[e]&&(i[e]=this);return i},t.eventCallback=function(t,e,i,n){if(\"on\"===(t||\"\").substr(0,2)){var s=this.vars;if(1===arguments.length)return s[t];null==e?delete s[t]:(s[t]=e,s[t+\"Params\"]=c(i)&&-1!==i.join(\"\").indexOf(\"{self}\")?this._swapSelfInParams(i):i,s[t+\"Scope\"]=n),\"onUpdate\"===t&&(this._onUpdate=e)}return this},t.delay=function(t){return arguments.length?(this._timeline.smoothChildTiming&&this.startTime(this._startTime+t-this._delay),this._delay=t,this):this._delay},t.duration=function(t){return arguments.length?(this._duration=this._totalDuration=t,this._uncache(!0),this._timeline.smoothChildTiming&&0this._duration?this._duration:t,e)):this._time},t.totalTime=function(t,e,i){if(_||A.wake(),!arguments.length)return this._totalTime;if(this._timeline){if(t<0&&!i&&(t+=this.totalDuration()),this._timeline.smoothChildTiming){this._dirty&&this.totalDuration();var n=this._totalDuration,s=this._timeline;if(nn;)i=i._prev;return i?(t._next=i._next,i._next=t):(t._next=this._first,this._first=t),t._next?t._next._prev=t:this._last=t,t._prev=i,this._timeline&&this._uncache(!0),this},t._remove=function(t,e){return t.timeline===this&&(e||t._enabled(!1,!0),t.timeline=null,t._prev?t._prev._next=t._next:this._first===t&&(this._first=t._next),t._next?t._next._prev=t._prev:this._last===t&&(this._last=t._prev),this._timeline&&this._uncache(!0)),this},t.render=function(t,e,i){var n,s=this._first;for(this._totalTime=this._time=this._rawPrevTime=t;s;)n=s._next,(s._active||t>=s._startTime&&!s._paused)&&(s._reversed?s.render((s._dirty?s.totalDuration():s._totalDuration)-(t-s._startTime)*s._timeScale,e,i):s.render((t-s._startTime)*s._timeScale,e,i)),s=n},t.rawTime=function(){return _||A.wake(),this._totalTime};var R=p(\"TweenLite\",function(t,e,i){if(C.call(this,e,i),this.render=R.prototype.render,null==t)throw\"Cannot tween a null target.\";this.target=t=\"string\"==typeof t&&R.selector(t)||t;var n,s,r,a=t.jquery||t.length&&t!==f&&t[0]&&(t[0]===f||t[0].nodeType&&t[0].style&&!t.nodeType),o=this.vars.overwrite;if(this._overwrite=o=null==o?Y[R.defaultOverwrite]:\"number\"==typeof o?o>>0:Y[o],(a||t instanceof Array||t.push&&c(t))&&\"number\"!=typeof t[0])for(this._targets=r=l.call(t,0),this._propLookup=[],this._siblings=[],n=0;r.length>n;n++)(s=r[n])?\"string\"!=typeof s?s.length&&s!==f&&s[0]&&(s[0]===f||s[0].nodeType&&s[0].style&&!s.nodeType)?(r.splice(n--,1),this._targets=r=r.concat(l.call(s,0))):(this._siblings[n]=B(s,this,!1),1===o&&1=a._startTime&&a._startTime+a.totalDuration()/a._timeScale>h&&((p||!a._initted)&&h-a._startTime<=2e-10||(c[u++]=a)));for(f=u;-1<--f;)a=c[f],2===n&&a._kill(i,t)&&(r=!0),(2!==n||!a._firstPT&&a._initted)&&a._enabled(!1,!1)&&(r=!0);return r},H=function(t,e,i){for(var n=t._timeline,s=n._timeScale,r=t._startTime;n._timeline;){if(r+=n._startTime,s*=n._timeScale,n._paused)return-100;n=n._timeline}return e<(r/=s)?r-e:i&&r===e||!t._initted&&r-e<2*g?g:(r+=t.totalDuration()/t._timeScale/s)>e+g?0:r-e-g};t._init=function(){var t,e,i,n,s,r=this.vars,a=this._overwrittenProps,o=this._duration,l=!!r.immediateRender,h=r.ease;if(r.startAt){for(n in this._startAt&&(this._startAt.render(-1,!0),this._startAt.kill()),s={},r.startAt)s[n]=r.startAt[n];if(s.overwrite=!1,s.immediateRender=!0,s.lazy=l&&!1!==r.lazy,s.startAt=s.delay=null,this._startAt=R.to(this.target,0,s),l)if(0o.pr;)n=n._next;(o._prev=n?n._prev:r)?o._prev._next=o:s=o,(o._next=n)?n._prev=o:r=o,o=a}o=e._firstPT=s}for(;o;)o.pg&&\"function\"==typeof o.t[t]&&o.t[t]()&&(i=!0),o=o._next;return i},V.activate=function(t){for(var e=t.length;-1<--e;)t[e].API===V.API&&(X[(new t[e])._propName]=t[e]);return!0},r.plugin=function(t){if(!(t&&t.propName&&t.init&&t.API))throw\"illegal plugin definition.\";var e,i=t.propName,n=t.priority||0,s=t.overwriteProps,r={init:\"_onInitTween\",set:\"setRatio\",kill:\"_kill\",round:\"_roundProps\",initAll:\"_onInitAllProps\"},a=p(\"plugins.\"+i.charAt(0).toUpperCase()+i.substr(1)+\"Plugin\",function(){V.call(this,i,n),this._overwriteProps=s||[]},!0===t.global),o=a.prototype=new V(i);for(e in(o.constructor=a).API=t.API,r)\"function\"==typeof t[e]&&(o[r[e]]=t[e]);return a.version=t.version,V.activate([a]),a},h=f._gsQueue){for(O=0;h.length>O;O++)h[O]();for(t in v)v[t].func||f.console.log(\"GSAP encountered missing dependency: com.greensock.\"+t)}_=!1}}(window)},{}],11:[function(t,e,i){\"use strict\";(window._gsQueue||(window._gsQueue=[])).push(function(){window._gsDefine(\"easing.Back\",[\"easing.Ease\"],function(m){function t(t,e){var i=c(\"easing.\"+t,function(){},!0),n=i.prototype=new m;return n.constructor=i,n.getRatio=e,i}function e(t,e,i,n){var s=c(\"easing.\"+t,{easeOut:new e,easeIn:new i,easeInOut:new n},!0);return u(s,t),s}function g(t,e,i){this.t=t,this.v=e,i&&(((this.next=i).prev=this).c=i.v-e,this.gap=i.t-t)}function i(t,e){var i=c(\"easing.\"+t,function(t){this._p1=t||0===t?t:1.70158,this._p2=1.525*this._p1},!0),n=i.prototype=new m;return n.constructor=i,n.getRatio=e,n.config=function(t){return new i(t)},i}var n,s,r,a=window.GreenSockGlobals||window,o=a.com.greensock,l=2*Math.PI,h=Math.PI/2,c=o._class,u=m.register||function(){},p=e(\"Back\",i(\"BackOut\",function(t){return--t*t*((this._p1+1)*t+this._p1)+1}),i(\"BackIn\",function(t){return t*t*((this._p1+1)*t-this._p1)}),i(\"BackInOut\",function(t){return(t*=2)<1?.5*t*t*((this._p2+1)*t-this._p2):.5*((t-=2)*t*((this._p2+1)*t+this._p2)+2)})),f=c(\"easing.SlowMo\",function(t,e,i){e=e||0===e?e:.7,null==t?t=.7:1t?this._calcEnd?1-(t=1-t/this._p1)*t:e-(t=1-t/this._p1)*t*t*t*e:t>this._p3?this._calcEnd?1-(t=(t-this._p3)/this._p1)*t:e+(t-e)*(t=(t-this._p3)/this._p1)*t*t*t:this._calcEnd?1:e},f.ease=new f(.7,.7),d.config=f.config=function(t,e,i){return new f(t,e,i)},(d=(n=c(\"easing.SteppedEase\",function(t){t=t||1,this._p1=1/t,this._p2=t+1},!0)).prototype=new m).constructor=n,d.getRatio=function(t){return t<0?t=0:1<=t&&(t=.999999999),(this._p2*t>>0)*this._p1},d.config=n.config=function(t){return new n(t)},(d=(s=c(\"easing.RoughEase\",function(t){for(var e,i,n,s,r,a,o=(t=t||{}).taper||\"none\",l=[],h=0,c=0|(t.points||20),u=c,p=!1!==t.randomize,f=!0===t.clamp,d=t.template instanceof m?t.template:null,_=\"number\"==typeof t.strength?.4*t.strength:.4;-1<--u;)e=p?Math.random():1/c*u,i=d?d.getRatio(e):e,n=\"none\"===o?_:\"out\"===o?(s=1-e)*s*_:\"in\"===o?e*e*_:.5*(s=e<.5?2*e:2*(1-e))*s*_,p?i+=Math.random()*n-.5*n:u%2?i+=.5*n:i-=.5*n,f&&(1e.t){for(;e.next&&t>=e.t;)e=e.next;e=e.prev}else for(;e.prev&&e.t>=t;)e=e.prev;return(this._prev=e).v+(t-e.t)/e.gap*e.c},d.config=function(t){return new s(t)},s.ease=new s,e(\"Bounce\",t(\"BounceOut\",function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375}),t(\"BounceIn\",function(t){return 1/2.75>(t=1-t)?1-7.5625*t*t:t<2/2.75?1-(7.5625*(t-=1.5/2.75)*t+.75):t<2.5/2.75?1-(7.5625*(t-=2.25/2.75)*t+.9375):1-(7.5625*(t-=2.625/2.75)*t+.984375)}),t(\"BounceInOut\",function(t){var e=t<.5;return t=(t=e?1-2*t:2*t-1)<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375,e?.5*(1-t):.5*t+.5})),e(\"Circ\",t(\"CircOut\",function(t){return Math.sqrt(1- --t*t)}),t(\"CircIn\",function(t){return-(Math.sqrt(1-t*t)-1)}),t(\"CircInOut\",function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)})),e(\"Elastic\",(r=function(t,e,i){var n=c(\"easing.\"+t,function(t,e){this._p1=t||1,this._p2=e||i,this._p3=this._p2/l*(Math.asin(1/this._p1)||0)},!0),s=n.prototype=new m;return s.constructor=n,s.getRatio=e,s.config=function(t,e){return new n(t,e)},n})(\"ElasticOut\",function(t){return this._p1*Math.pow(2,-10*t)*Math.sin((t-this._p3)*l/this._p2)+1},.3),r(\"ElasticIn\",function(t){return-(this._p1*Math.pow(2,10*--t)*Math.sin((t-this._p3)*l/this._p2))},.3),r(\"ElasticInOut\",function(t){return(t*=2)<1?-.5*this._p1*Math.pow(2,10*--t)*Math.sin((t-this._p3)*l/this._p2):.5*this._p1*Math.pow(2,-10*--t)*Math.sin((t-this._p3)*l/this._p2)+1},.45)),e(\"Expo\",t(\"ExpoOut\",function(t){return 1-Math.pow(2,-10*t)}),t(\"ExpoIn\",function(t){return Math.pow(2,10*(t-1))-.001}),t(\"ExpoInOut\",function(t){return(t*=2)<1?.5*Math.pow(2,10*(t-1)):.5*(2-Math.pow(2,-10*(t-1)))})),e(\"Sine\",t(\"SineOut\",function(t){return Math.sin(t*h)}),t(\"SineIn\",function(t){return 1-Math.cos(t*h)}),t(\"SineInOut\",function(t){return-.5*(Math.cos(Math.PI*t)-1)})),c(\"easing.EaseLookup\",{find:function(t){return m.map[t]}},!0),u(a.SlowMo,\"SlowMo\",\"ease,\"),u(s,\"RoughEase\",\"ease,\"),u(n,\"SteppedEase\",\"ease,\"),p},!0)}),window._gsDefine&&window._gsQueue.pop()()},{}],12:[function(t,e,i){\"use strict\";var Lt=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t};(window._gsQueue||(window._gsQueue=[])).push(function(){window._gsDefine(\"plugins.CSSPlugin\",[\"plugins.TweenPlugin\",\"TweenLite\"],function(r,p){function J(){r.call(this,\"css\"),this._overwriteProps.length=0,this.setRatio=J.prototype.setRatio}var d,T,k,f,_={},t=J.prototype=new r(\"css\");(t.constructor=J).version=\"1.12.1\",J.API=2,J.defaultTransformPerspective=0,J.defaultSkewType=\"compensated\",J.suffixMap={top:t=\"px\",right:t,bottom:t,left:t,width:t,height:t,fontSize:t,padding:t,margin:t,perspective:t,lineHeight:\"\"};function a(t,e){return e.toUpperCase()}function o(t){return E.test(\"string\"==typeof t?t:(t.currentStyle?t.currentStyle.filter:t.style.filter)||\"\")?parseFloat(RegExp.$1)/100:1}function m(t){window.console&&console.log(t)}function P(t,e){var i,n,s=(e=e||V).style;if(void 0!==s[t])return t;for(t=t.charAt(0).toUpperCase()+t.substr(1),i=[\"O\",\"Moz\",\"ms\",\"Ms\",\"Webkit\"],n=5;-1<--n&&void 0===s[i[n]+t];);return 0<=n?(G=\"-\"+(K=3===n?\"ms\":i[n]).toLowerCase()+\"-\",K+t):null}function g(t,e){var i,n,s={};if(e=e||nt(t,null))if(i=e.length)for(;-1<--i;)s[e[i].replace(Y,a)]=e.getPropertyValue(e[i]);else for(i in e)s[i]=e[i];else if(e=t.currentStyle||t.style)for(i in e)\"string\"==typeof i&&void 0===s[i]&&(s[i.replace(Y,a)]=e[i]);return Z||(s.opacity=o(t)),n=St(t,e,!1),s.rotation=n.rotation,s.skewX=n.skewX,s.scaleX=n.scaleX,s.scaleY=n.scaleY,s.x=n.x,s.y=n.y,kt&&(s.z=n.z,s.rotationX=n.rotationX,s.rotationY=n.rotationY,s.scaleZ=n.scaleZ),s.filters&&delete s.filters,s}function v(t,e,i,n,s){var r,a,o,l={},h=t.style;for(a in i)\"cssText\"!==a&&\"length\"!==a&&isNaN(a)&&(e[a]!==(r=i[a])||s&&s[a])&&-1===a.indexOf(\"Origin\")&&(\"number\"==typeof r||\"string\"==typeof r)&&(l[a]=\"auto\"!==r||\"left\"!==a&&\"top\"!==a?\"\"!==r&&\"auto\"!==r&&\"none\"!==r||\"string\"!=typeof e[a]||\"\"===e[a].replace(c,\"\")?r:0:at(t,a),void 0!==h[a]&&(o=new ft(h,a,h[a],o)));if(n)for(a in n)\"className\"!==a&&(l[a]=n[a]);return{difs:l,firstMPT:o}}function w(t,e){null!=t&&\"\"!==t&&\"auto\"!==t&&\"auto auto\"!==t||(t=\"0 0\");var i=t.split(\" \"),n=-1!==t.indexOf(\"left\")?\"0%\":-1!==t.indexOf(\"right\")?\"100%\":i[0],s=-1!==t.indexOf(\"top\")?\"0%\":-1!==t.indexOf(\"bottom\")?\"100%\":i[1];return null==s?s=\"0\":\"center\"===s&&(s=\"50%\"),(\"center\"===n||isNaN(parseFloat(n))&&-1===(n+\"\").indexOf(\"=\"))&&(n=\"50%\"),e&&(e.oxp=-1!==n.indexOf(\"%\"),e.oyp=-1!==s.indexOf(\"%\"),e.oxr=\"=\"===n.charAt(1),e.oyr=\"=\"===s.charAt(1),e.ox=parseFloat(n.replace(c,\"\")),e.oy=parseFloat(s.replace(c,\"\"))),n+\" \"+s+(2>16,255&t>>8,255&t]:(\",\"===t.charAt(t.length-1)&&(t=t.substr(0,t.length-1)),ht[t]?ht[t]:\"#\"===t.charAt(0)?(4===t.length&&(t=\"#\"+(e=t.charAt(1))+e+(i=t.charAt(2))+i+(n=t.charAt(3))+n),[(t=parseInt(t.substr(1),16))>>16,255&t>>8,255&t]):(\"hsl\"===t.substr(0,3)?(t=t.match(I),s=Number(t[0])%360/360,r=Number(t[1])/100,e=2*(a=Number(t[2])/100)-(i=a<=.5?a*(1+r):a+r-a*r),3a\",!!(e=n.getElementsByTagName(\"a\")[0])&&/^0.55/.test(e.style.opacity)),G=\"\",K=\"\",nt=H.defaultView?H.defaultView.getComputedStyle:function(){},st=J.getStyle=function(t,e,i,n,s){var r;return Z||\"opacity\"!==e?(!n&&t.style[e]?r=t.style[e]:(i=i||nt(t))?r=i[e]||i.getPropertyValue(e)||i.getPropertyValue(e.replace(u,\"-$1\").toLowerCase()):t.currentStyle&&(r=t.currentStyle[e]),null==s||r&&\"none\"!==r&&\"auto\"!==r&&\"auto auto\"!==r?r:s):o(t)},rt=s.convertToPixels=function(t,e,i,n,s){if(\"px\"===n||!n)return i;if(\"auto\"===n||!i)return 0;var r,a,o,l=$.test(e),h=t,c=V.style,u=i<0;if(u&&(i=-i),\"%\"===n&&-1!==e.indexOf(\"border\"))r=i/100*(l?t.clientWidth:t.clientHeight);else{if(c.cssText=\"border:0 solid red;position:\"+st(t,\"position\")+\";line-height:0;\",\"%\"!==n&&h.appendChild)c[l?\"borderLeftWidth\":\"borderTopWidth\"]=i+n;else{if(a=(h=t.parentNode||H.body)._gsCache,o=p.ticker.frame,a&&l&&a.time===o)return a.width*i/100;c[l?\"width\":\"height\"]=i+n}h.appendChild(V),r=parseFloat(V[l?\"offsetWidth\":\"offsetHeight\"]),h.removeChild(V),l&&\"%\"===n&&!1!==J.cacheWidths&&((a=h._gsCache=h._gsCache||{}).time=o,a.width=r/i*100),0!==r||s||(r=rt(t,e,i,n,!0))}return u?-r:r},at=s.calculateOffset=function(t,e,i){if(\"absolute\"!==st(t,\"position\",i))return 0;var n=\"left\"===e?\"Left\":\"Top\",s=st(t,\"margin\"+n,i);return t[\"offset\"+n]-(rt(t,e,parseFloat(s),s.replace(X,\"\"))||0)},ot={width:[\"Left\",\"Right\"],height:[\"Top\",\"Bottom\"]},lt=[\"marginLeft\",\"marginRight\",\"marginTop\",\"marginBottom\"],ht={aqua:[0,255,255],lime:[0,255,0],silver:[192,192,192],black:[0,0,0],maroon:[128,0,0],teal:[0,128,128],blue:[0,0,255],navy:[0,0,128],white:[255,255,255],fuchsia:[255,0,255],olive:[128,128,0],yellow:[255,255,0],orange:[255,165,0],gray:[128,128,128],purple:[128,0,128],green:[0,128,0],red:[255,0,0],pink:[255,192,203],cyan:[0,255,255],transparent:[255,255,255,0]},ct=\"(?:\\\\b(?:(?:rgb|rgba|hsl|hsla)\\\\(.+?\\\\))|\\\\B#.+?\\\\b\";for(t in ht)ct+=\"|\"+t+\"\\\\b\";ct=RegExp(ct+\")\",\"gi\");function ut(t,e,r,a){if(null==t)return function(t){return t};var o,l=e?(t.match(ct)||[\"\"])[0]:\"\",h=t.split(l).join(\"\").match(L)||[],c=t.substr(0,t.indexOf(h[0])),u=\")\"===t.charAt(t.length-1)?\")\":\"\",p=-1!==t.indexOf(\" \")?\" \":\",\",f=h.length,d=0n;n++)s[n]=o(s[n]);return s.join(\",\")}if(e=(t.match(ct)||[l])[0],n=(i=t.split(e).join(\"\").match(L)||[]).length,f>n--)for(;f>++n;)i[n]=r?i[0|(n-1)/2]:h[n];return c+i.join(p)+p+e+u+(-1!==t.indexOf(\"inset\")?\" inset\":\"\")}:function(t){var e,i,n;if(\"number\"==typeof t)t+=d;else if(a&&B.test(t)){for(i=t.replace(B,\"|\").split(\"|\"),n=0;i.length>n;n++)i[n]=o(i[n]);return i.join(\",\")}if(n=(e=t.match(L)||[]).length,f>n--)for(;f>++n;)e[n]=r?e[0|(n-1)/2]:h[n];return c+e.join(p)+u}:function(t){return t}}function pt(h){return h=h.split(\",\"),function(t,e,i,n,s,r,a){var o,l=(e+\"\").split(\" \");for(a={},o=0;o<4;o++)a[h[o]]=l[o]=l[o]||l[(o-1)/2>>0];return n.parse(t,a,s,r)}}var ft=(s._setPluginRatio=function(t){this.plugin.setRatio(t);for(var e,i,n,s,r=this.data,a=r.proxy,o=r.firstMPT;o;)e=a[o.v],o.r?e=Math.round(e):e<1e-6&&-1e-6n;n++)s+=i[\"xn\"+n]+i[\"xs\"+(n+1)];i.e=s}}else i.e=i.s+i.xs0;o=o._next}},function(t,e,i,n,s){this.t=t,this.p=e,this.v=i,this.r=s,n&&((n._prev=this)._next=n)}),dt=(s._parseToProxy=function(t,e,i,n,s,r){var a,o,l,h,c,u=n,p={},f={},d=i._transform,_=U;for(i._transform=null,U=e,n=c=i.parse(t,e,n,s),U=_,r&&(i._transform=d,u&&(u._prev=null,u._prev&&(u._prev._next=null)));n&&n!==u;){if(n.type<=1&&(f[o=n.p]=n.s+n.c,p[o]=n.s,r||(h=new ft(n,\"s\",o,h,n.r),n.c=0),1===n.type))for(a=n.l;0<--a;)l=\"xn\"+a,f[o=n.p+\"_\"+l]=n.data[l],p[o]=n[l],r||(h=new ft(n,l,o,h,n.rxp[l]));n=n._next}return{proxy:p,end:f,firstMPT:h,pt:c}},s.CSSPropTween=function(t,e,i,n,s,r,a,o,l,h,c){this.t=t,this.p=e,this.s=i,this.c=n,this.n=a||e,t instanceof dt||f.push(this.n),this.r=o,this.type=r||0,l&&(this.pr=l,d=!0),this.b=void 0===h?i:h,this.e=void 0===c?i+n:c,s&&((this._next=s)._prev=this)}),_t=J.parseComplex=function(t,e,i,n,s,r,a,o,l,h){a=new dt(t,e,0,0,a,h?2:1,null,!1,o,i=i||r||\"\",n),n+=\"\";var c,u,p,f,d,_,m,g,v,w,y,x,b=i.split(\", \").join(\",\").split(\" \"),T=n.split(\", \").join(\",\").split(\" \"),k=b.length,P=!1!==C;for(-1===n.indexOf(\",\")&&-1===i.indexOf(\",\")||(b=b.join(\" \").replace(B,\", \").split(\" \"),T=T.join(\" \").replace(B,\", \").split(\" \"),k=b.length),k!==T.length&&(k=(b=(r||\"\").split(\" \")).length),a.plugin=l,a.setRatio=h,c=0;cu;u++)y=_[u],w=f.indexOf(y,p),a.appendXtra(f.substr(p,w-p),Number(y),S(m[u],y),\"\",P&&\"px\"===f.substr(w+y.length,2),0===u),p=w+y.length;a[\"xs\"+a.l]+=f.substr(p)}else a[\"xs\"+a.l]+=a.l?\" \"+f:f;if(-1!==n.indexOf(\"=\")&&a.data){for(x=a.xs0+a.data.s,c=1;a.l>c;c++)x+=a[\"xs\"+c]+a.data[\"xn\"+c];a.e=x+a[\"xs\"+c]}return a.l||(a.type=-1,a.xs0=a.e),a.xfirst||a},mt=9;for((t=dt.prototype).l=t.pr=0;0<--mt;)t[\"xn\"+mt]=0,t[\"xs\"+mt]=\"\";t.xs0=\"\",t._next=t._prev=t.xfirst=t.data=t.plugin=t.setRatio=t.rxp=null,t.appendXtra=function(t,e,i,n,s,r){var a=this,o=a.l;return a[\"xs\"+o]+=r&&o?\" \"+t:t||\"\",i||0===o||a.plugin?(a.l++,a.type=a.setRatio?2:1,a[\"xs\"+a.l]=n||\"\",0n;n++)e.prefix=0===n&&e.prefix,e.defaultValue=i[n]||r,new gt(s[n],e)};(t=gt.prototype).parseComplex=function(t,e,i,n,s,r){var a,o,l,h,c,u=this.keyword;if(this.multi&&(B.test(i)||B.test(e)?(o=e.replace(B,\"|\").split(\"|\"),l=i.replace(B,\"|\").split(\"|\")):u&&(o=[e],l=[i])),l){for(h=l.length>o.length?l.length:o.length,a=0;aH[a]&&H[a]>-W&&(H[a]=0);return i&&(t._gsTransform=H),H},Ot=s.set3DTransformRatio=function(t){var e,i,n,s,r,a,o,l,h,c,u,p,f,d,_,m,g,v,w,y,x,b,T,k=this.data,P=this.t.style,S=k.rotation*et,O=k.scaleX,C=k.scaleY,A=k.scaleZ,M=k.perspective;if(1!==t&&0!==t||\"auto\"!==k.force3D||k.rotationY||k.rotationX||1!==A||M||k.z){if(R&&(O<1e-4&&-1e-4b;b++)this.p.indexOf(\"border\")&&(g[b]=P(g[b])),-1!==(o=a=st(t,g[b],k,!1,\"0px\")).indexOf(\" \")&&(o=(a=o.split(\" \"))[0],a=a[1]),l=r=x[b],h=parseFloat(o),p=o.substr((h+\"\").length),\"\"===(u=(f=\"=\"===l.charAt(1))?(c=parseInt(l.charAt(0)+\"1\",10),l=l.substr(2),c*=parseFloat(l),l.substr((c+\"\").length-(c<0?1:0))||\"\"):(c=parseFloat(l),l.substr((c+\"\").length)))&&(u=T[i]||p),u!==p&&(d=rt(t,\"borderLeft\",h,p),_=rt(t,\"borderTop\",h,p),a=\"%\"===u?(o=d/w*100+\"%\",_/y*100+\"%\"):\"em\"===u?(o=d/(m=rt(t,\"borderLeft\",1,\"em\"))+\"em\",_/m+\"em\"):(o=d+\"px\",_+\"px\"),f&&(l=parseFloat(o)+c+u,r=parseFloat(a)+c+u)),s=_t(v,g[b],o+\" \"+a,l+\" \"+r,!1,\"0px\",s);return s},prefix:!0,formatter:ut(\"0px 0px 0px 0px\",!1,!0)}),vt(\"backgroundPosition\",{defaultValue:\"0 0\",parser:function(t,e,i,n,s,r){var a,o,l,h,c,u,p=\"background-position\",f=k||nt(t,null),d=this.format((f?D?f.getPropertyValue(p+\"-x\")+\" \"+f.getPropertyValue(p+\"-y\"):f.getPropertyValue(p):t.currentStyle.backgroundPositionX+\" \"+t.currentStyle.backgroundPositionY)||\"0 0\"),_=this.format(e);if(-1!==d.indexOf(\"%\")!=(-1!==_.indexOf(\"%\"))&&((u=st(t,\"backgroundImage\").replace(F,\"\"))&&\"none\"!==u)){for(a=d.split(\" \"),o=_.split(\" \"),W.setAttribute(\"src\",u),l=2;-1<--l;)(h=-1!==(d=a[l]).indexOf(\"%\"))!=(-1!==o[l].indexOf(\"%\"))&&(c=0===l?t.offsetWidth-W.width:t.offsetHeight-W.height,a[l]=h?parseFloat(d)/100*c+\"px\":parseFloat(d)/c*100+\"%\");d=a.join(\" \")}return this.parseComplex(t.style,d,_,s,r)},formatter:w}),vt(\"backgroundSize\",{defaultValue:\"0 0\",formatter:w}),vt(\"perspective\",{defaultValue:\"0px\",prefix:!0}),vt(\"perspectiveOrigin\",{defaultValue:\"50% 50%\",prefix:!0}),vt(\"transformStyle\",{prefix:!0}),vt(\"backfaceVisibility\",{prefix:!0}),vt(\"userSelect\",{prefix:!0}),vt(\"margin\",{parser:pt(\"marginTop,marginRight,marginBottom,marginLeft\")}),vt(\"padding\",{parser:pt(\"paddingTop,paddingRight,paddingBottom,paddingLeft\")}),vt(\"clip\",{defaultValue:\"rect(0px,0px,0px,0px)\",parser:function(t,e,i,n,s,r){var a,o,l;return e=D<9?(o=t.currentStyle,l=D<8?\" \":\",\",a=\"rect(\"+o.clipTop+l+o.clipRight+l+o.clipBottom+l+o.clipLeft+\")\",this.format(e).split(\",\").join(l)):(a=this.format(st(t,this.p,k,!1,this.dflt)),this.format(e)),this.parseComplex(t.style,a,e,s,r)}}),vt(\"textShadow\",{defaultValue:\"0px 0px 0px #999\",color:!0,multi:!0}),vt(\"autoRound,strictUnits\",{parser:function(t,e,i,n,s){return s}}),vt(\"border\",{defaultValue:\"0px solid #000\",parser:function(t,e,i,n,s,r){return this.parseComplex(t.style,this.format(st(t,\"borderTopWidth\",k,!1,\"0px\")+\" \"+st(t,\"borderTopStyle\",k,!1,\"solid\")+\" \"+st(t,\"borderTopColor\",k,!1,\"#000\")),this.format(e),s,r)},color:!0,formatter:function(t){var e=t.split(\" \");return e[0]+\" \"+(e[1]||\"solid\")+\" \"+(t.match(ct)||[\"#000\"])[0]}}),vt(\"borderWidth\",{parser:pt(\"borderTopWidth,borderRightWidth,borderBottomWidth,borderLeftWidth\")}),vt(\"float,cssFloat,styleFloat\",{parser:function(t,e,i,n,s){var r=t.style,a=\"cssFloat\"in r?\"cssFloat\":\"styleFloat\";return new dt(r,a,0,0,s,-1,i,!1,0,r[a],e)}});function At(t){var e,i=this.t,n=i.filter||st(this.data,\"filter\"),s=0|this.s+this.c*t;100==s&&(e=-1===n.indexOf(\"atrix(\")&&-1===n.indexOf(\"radient(\")&&-1===n.indexOf(\"oader(\")?(i.removeAttribute(\"filter\"),!st(this.data,\"filter\")):(i.filter=n.replace(h,\"\"),!0)),e||(this.xn1&&(i.filter=n=n||\"alpha(opacity=\"+s+\")\"),-1===n.indexOf(\"pacity\")?0==s&&this.xn1||(i.filter=n+\" alpha(opacity=\"+s+\")\"):i.filter=n.replace(E,\"opacity=\"+s))}vt(\"opacity,alpha,autoAlpha\",{defaultValue:\"1\",parser:function(t,e,i,n,s,r){var a=parseFloat(st(t,\"opacity\",k,!1,\"1\")),o=t.style,l=\"autoAlpha\"===i;return\"string\"==typeof e&&\"=\"===e.charAt(1)&&(e=(\"-\"===e.charAt(0)?-1:1)*parseFloat(e.substr(2))+a),l&&1===a&&\"hidden\"===st(t,\"visibility\",k)&&0!==e&&(a=0),Z?s=new dt(o,\"opacity\",a,e-a,s):((s=new dt(o,\"opacity\",100*a,100*(e-a),s)).xn1=l?1:0,o.zoom=1,s.type=2,s.b=\"alpha(opacity=\"+s.s+\")\",s.e=\"alpha(opacity=\"+(s.s+s.c)+\")\",s.data=t,s.plugin=r,s.setRatio=At),l&&((s=new dt(o,\"visibility\",0,0,s,-1,null,!1,0,0!==a?\"inherit\":\"hidden\",0===e?\"hidden\":\"inherit\")).xs0=\"inherit\",n._overwriteProps.push(s.n),n._overwriteProps.push(i)),s}});function Mt(t,e){e&&(t.removeProperty?(\"ms\"===e.substr(0,2)&&(e=\"M\"+e.substr(1)),t.removeProperty(e.replace(u,\"-$1\").toLowerCase())):t.removeAttribute(e))}function Rt(t){if(this.t._gsClassPT=this,1===t||0===t){this.t.setAttribute(\"class\",0===t?this.b:this.e);for(var e=this.data,i=this.t.style;e;)e.v?i[e.p]=e.v:Mt(i,e.p),e=e._next;1===t&&this.t._gsClassPT===this&&(this.t._gsClassPT=null)}else this.t.getAttribute(\"class\")!==this.e&&this.t.setAttribute(\"class\",this.e)}vt(\"className\",{parser:function(t,e,i,n,s,r,a){var o,l,h,c,u,p=t.getAttribute(\"class\")||\"\",f=t.style.cssText;if((s=n._classNamePT=new dt(t,i,0,0,s,2)).setRatio=Rt,s.pr=-11,d=!0,s.b=p,l=g(t,k),h=t._gsClassPT){for(c={},u=h.data;u;)c[u.p]=1,u=u._next;h.setRatio(1)}return(t._gsClassPT=s).e=\"=\"!==e.charAt(1)?e:p.replace(RegExp(\"\\\\s*\\\\b\"+e.substr(2)+\"\\\\b\"),\"\")+(\"+\"===e.charAt(0)?\" \"+e.substr(2):\"\"),n._tween._duration&&(t.setAttribute(\"class\",s.e),o=v(t,l,g(t),a,c),t.setAttribute(\"class\",p),s.data=o.firstMPT,t.style.cssText=f,s=s.xfirst=n.parse(t,o.difs,s,r)),s}});function Dt(t){if((1===t||0===t)&&this.data._totalTime===this.data._totalDuration&&\"isFromStart\"!==this.data.data){var e,i,n,s,r=this.t.style,a=_.transform.parse;if(\"all\"===this.e)s=!(r.cssText=\"\");else for(n=(e=this.e.split(\",\")).length;-1<--n;)i=e[n],_[i]&&(_[i].parse===a?s=!0:i=\"transformOrigin\"===i?Tt:_[i].p),Mt(r,i);s&&(Mt(r,xt),this.t._gsTransform&&delete this.t._gsTransform)}}for(vt(\"clearProps\",{parser:function(t,e,i,n,s){return(s=new dt(t,i,0,0,s,2)).setRatio=Dt,s.e=e,s.pr=-10,s.data=n._tween,d=!0,s}}),t=\"bezier,throwProps,physicsProps,physics2D\".split(\",\"),mt=t.length;mt--;)!function(t){var l;_[t]||(l=t.charAt(0).toUpperCase()+t.substr(1)+\"Plugin\",vt(t,{parser:function(t,e,i,n,s,r,a){var o=(window.GreenSockGlobals||window).com.greensock.plugins[l];return o?(o._cssRegister(),_[i].parse(t,e,i,n,s,r,a)):(m(\"Error: \"+l+\" js file not loaded.\"),s)}}))}(t[mt]);(t=J.prototype)._firstPT=null,t._onInitTween=function(t,e,i){if(!t.nodeType)return!1;this._target=t,this._tween=i,this._vars=e,C=e.autoRound,d=!1,T=e.suffixMap||J.suffixMap,k=nt(t,\"\"),f=this._overwriteProps;var n,s,r,a,o,l,h,c,u,p=t.style;if(b&&\"\"===p.zIndex&&(\"auto\"!==(n=st(t,\"zIndex\",k))&&\"\"!==n||this._addLazySet(p,\"zIndex\",0)),\"string\"==typeof e&&(a=p.cssText,n=g(t,k),p.cssText=a+\";\"+e,n=v(t,n,g(t)).difs,!Z&&z.test(e)&&(n.opacity=parseFloat(RegExp.$1)),e=n,p.cssText=a),this._firstPT=s=this.parse(t,e,null),this._transformType){for(u=3===this._transformType,xt?A&&(b=!0,\"\"===p.zIndex&&(\"auto\"!==(h=st(t,\"zIndex\",k))&&\"\"!==h||this._addLazySet(p,\"zIndex\",0)),M&&this._addLazySet(p,\"WebkitBackfaceVisibility\",this._vars.WebkitBackfaceVisibility||(u?\"visible\":\"hidden\"))):p.zoom=1,r=s;r&&r._next;)r=r._next;c=new dt(t,\"transform\",0,0,null,2),this._linkCSSP(c,null,r),c.setRatio=u&&kt?Ot:xt?Ct:wt,c.data=this._transform||St(t,k,!0),f.pop()}if(d){for(;s;){for(l=s._next,r=a;r&&r.pr>s.pr;)r=r._next;(s._prev=r?r._prev:o)?s._prev._next=s:a=s,(s._next=r)?r._prev=s:o=s,s=l}this._firstPT=a}return!0},t.parse=function(t,e,i,n){var s,r,a,o,l,h,c,u,p,f,d=t.style;for(s in e)h=e[s],(r=_[s])?i=r.parse(t,h,s,this,i,n,e):(l=st(t,s,k)+\"\",p=\"string\"==typeof h,\"color\"===s||\"fill\"===s||\"stroke\"===s||-1!==s.indexOf(\"Color\")||p&&N.test(h)?(p||(h=(3<(h=O(h)).length?\"rgba(\":\"rgb(\")+h.join(\",\")+\")\"),i=_t(d,s,l,h,!0,\"transparent\",i,0,n)):!p||-1===h.indexOf(\" \")&&-1===h.indexOf(\",\")?(c=(a=parseFloat(l))||0===a?l.substr((a+\"\").length):\"\",\"\"!==l&&\"auto\"!==l||(c=\"width\"===s||\"height\"===s?(a=function(t,e,i){var n=parseFloat(\"width\"===e?t.offsetWidth:t.offsetHeight),s=ot[e],r=s.length;for(i=i||nt(t,null);-1<--r;)n-=parseFloat(st(t,\"padding\"+s[r],i,!0))||0,n-=parseFloat(st(t,\"border\"+s[r]+\"Width\",i,!0))||0;return n}(t,s,k),\"px\"):\"left\"===s||\"top\"===s?(a=at(t,s,k),\"px\"):(a=\"opacity\"!==s?0:1,\"\")),\"\"===(u=(f=p&&\"=\"===h.charAt(1))?(o=parseInt(h.charAt(0)+\"1\",10),h=h.substr(2),o*=parseFloat(h),h.replace(X,\"\")):(o=parseFloat(h),p&&h.substr((o+\"\").length)||\"\"))&&(u=s in T?T[s]:c),h=o||0===o?(f?o+a:o)+u:e[s],c!==u&&\"\"!==u&&(o||0===o)&&a&&(a=rt(t,s,a,c),\"%\"===u?(a/=rt(t,s,100,\"%\")/100,!0!==e.strictUnits&&(l=a+\"%\")):\"em\"===u?a/=rt(t,s,1,\"em\"):\"px\"!==u&&(o=rt(t,s,o,u),u=\"px\"),f&&(o||0===o)&&(h=o+a+u)),f&&(o+=a),!a&&0!==a||!o&&0!==o?void 0!==d[s]&&(h||\"NaN\"!=h+\"\"&&null!=h)?(i=new dt(d,s,o||a||0,0,i,-1,s,!1,0,l,h)).xs0=\"none\"!==h||\"display\"!==s&&-1===s.indexOf(\"Style\")?h:l:m(\"invalid \"+s+\" tween value: \"+e[s]):(i=new dt(d,s,a,o-a,i,0,s,!1!==C&&(\"px\"===u||\"zIndex\"===s),0,l,h)).xs0=u):i=_t(d,s,l,h,!0,null,i,0,n)),n&&i&&!i.plugin&&(i.plugin=n);return i},t.setRatio=function(t){var e,i,n,s=this._firstPT;if(1!==t||this._tween._time!==this._tween._duration&&0!==this._tween._time)if(t||this._tween._time!==this._tween._duration&&0!==this._tween._time||-1e-6===this._tween._rawPrevTime)for(;s;){if(e=s.c*t+s.s,s.r?e=Math.round(e):e<1e-6&&-1e-6n;n++)i+=s[\"xn\"+n]+s[\"xs\"+(n+1)];s.t[s.p]=i}else-1===s.type?s.t[s.p]=s.xs0:s.setRatio&&s.setRatio(t);else s.t[s.p]=e+s.xs0;s=s._next}else for(;s;)2!==s.type?s.t[s.p]=s.b:s.setRatio(t),s=s._next;else for(;s;)2!==s.type?s.t[s.p]=s.e:s.setRatio(t),s=s._next},t._enableTransforms=function(t){this._transformType=t||3===this._transformType?3:2,this._transform=this._transform||St(this._target,k,!0)};function It(){this.t[this.p]=this.e,this.data._linkCSSP(this,this._next,null,!0)}t._addLazySet=function(t,e,i){var n=this._firstPT=new dt(t,e,0,0,this._firstPT,2);n.e=i,n.setRatio=It,n.data=this},t._linkCSSP=function(t,e,i,n){return t&&(e&&(e._prev=t),t._next&&(t._next._prev=t._prev),t._prev?t._prev._next=t._next:this._firstPT===t&&(this._firstPT=t._next,n=!0),i?i._next=t:n||null!==this._firstPT||(this._firstPT=t),t._next=e,t._prev=i),t},t._kill=function(t){var e,i,n,s=t;if(t.autoAlpha||t.alpha){for(i in s={},t)s[i]=t[i];s.opacity=1,s.autoAlpha&&(s.visibility=1)}return t.className&&(e=this._classNamePT)&&((n=e.xfirst)&&n._prev?this._linkCSSP(n._prev,e._next,n._prev._prev):n===this._firstPT&&(this._firstPT=e._next),e._next&&this._linkCSSP(e._next,e._next._next,n._prev),this._classNamePT=null),r.prototype._kill.call(this,s)};function jt(t,e,i){var n,s,r,a;if(t.slice)for(s=t.length;-1<--s;)jt(t[s],e,i);else for(s=(n=t.childNodes).length;-1<--s;)a=(r=n[s]).type,r.style&&(e.push(g(r)),i&&i.push(r)),1!==a&&9!==a&&11!==a||!r.childNodes.length||jt(r,e,i)}return J.cascadeTo=function(t,e,i){var n,s,r,a=p.to(t,e,i),o=[a],l=[],h=[],c=[],u=p._internals.reservedProps;for(t=a._targets||a.target,jt(t,l,c),a.render(e,!0),jt(t,h),a.render(0,!0),a._enabled(!0),n=c.length;-1<--n;)if((s=v(c[n],l[n],h[n])).firstMPT){for(r in s=s.difs,i)u[r]&&(s[r]=i[r]);o.push(p.to(c[n],e,s))}return o},r.activate([J]),J},!0)}),window._gsDefine&&window._gsQueue.pop()()},{}],13:[function(t,e,i){\"use strict\";var n=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t};(window._gsQueue||(window._gsQueue=[])).push(function(){function r(t,e){var i=\"x\"===e?\"Width\":\"Height\",n=\"scroll\"+i,s=\"client\"+i,r=document.body;return t===o||t===a||t===r?Math.max(a[n],r[n])-(o[\"inner\"+i]||Math.max(a[s],r[s])):t[n]-t[\"offset\"+i]}var a=document.documentElement,o=window,t=window._gsDefine.plugin({propName:\"scrollTo\",API:2,version:\"1.7.3\",init:function(t,e,i){return this._wdw=t===o,this._target=t,this._tween=i,\"object\"!=(void 0===e?\"undefined\":n(e))&&(e={y:e}),this._autoKill=!1!==e.autoKill,this.x=this.xPrev=this.getX(),this.y=this.yPrev=this.getY(),null!=e.x?(this._addTween(this,\"x\",this.x,\"max\"===e.x?r(t,\"x\"):e.x,\"scrollTo_x\",!0),this._overwriteProps.push(\"scrollTo_x\")):this.skipX=!0,null!=e.y?(this._addTween(this,\"y\",this.y,\"max\"===e.y?r(t,\"y\"):e.y,\"scrollTo_y\",!0),this._overwriteProps.push(\"scrollTo_y\")):this.skipY=!0,!0},set:function(t){this._super.setRatio.call(this,t);var e=this._wdw||!this.skipX?this.getX():this.xPrev,i=this._wdw||!this.skipY?this.getY():this.yPrev,n=i-this.yPrev,s=e-this.xPrev;this._autoKill&&(!this.skipX&&(7e&&(this.skipX=!0),!this.skipY&&(7i&&(this.skipY=!0),this.skipX&&this.skipY&&this._tween.kill()),this._wdw?o.scrollTo(this.skipX?e:this.x,this.skipY?i:this.y):(this.skipY||(this._target.scrollTop=this.y),this.skipX||(this._target.scrollLeft=this.x)),this.xPrev=this.x,this.yPrev=this.y}}),e=t.prototype;t.max=r,e.getX=function(){return this._wdw?null!=o.pageXOffset?o.pageXOffset:null!=a.scrollLeft?a.scrollLeft:document.body.scrollLeft:this._target.scrollLeft},e.getY=function(){return this._wdw?null!=o.pageYOffset?o.pageYOffset:null!=a.scrollTop?a.scrollTop:document.body.scrollTop:this._target.scrollTop},e._kill=function(t){return t.scrollTo_x&&(this.skipX=!0),t.scrollTo_y&&(this.skipY=!0),this._super._kill.call(this,t)}}),window._gsDefine&&window._gsQueue.pop()()},{}]},{},[2]);"],"file":"wpr-admin.js"}
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/assets/js/wpr-cpcss-heartbeat.js b/wp-content/plugins/wp-rocket/assets/js/wpr-cpcss-heartbeat.js
new file mode 100644
index 00000000..b2195a8f
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/wpr-cpcss-heartbeat.js
@@ -0,0 +1,36 @@
+let cpcssHeartbeatCall;
+const cpcssHeartbeat = () => {
+ const xhttp = new XMLHttpRequest();
+ xhttp.onload = () => {
+ if ( 200 !== xhttp.status ) {
+ return;
+ }
+
+ const cpcs_heartbeat_response = JSON.parse( xhttp.response );
+ if ( false === cpcs_heartbeat_response.success ) {
+ stopCPCSSHeartbeat();
+ return;
+ }
+
+ if ( cpcs_heartbeat_response.success &&
+ 'cpcss_complete' === cpcs_heartbeat_response.data.status ) {
+ stopCPCSSHeartbeat();
+ return;
+ }
+
+ cpcssHeartbeatCall = setTimeout( () => {
+ cpcssHeartbeat();
+ }, 3000 );
+ };
+
+ xhttp.open( 'POST', ajaxurl, true );
+ xhttp.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
+ xhttp.send( "action=rocket_cpcss_heartbeat&_nonce=" + rocket_cpcss_heartbeat.nonce );
+}
+
+
+const stopCPCSSHeartbeat = () => {
+ clearTimeout( cpcssHeartbeatCall );
+}
+
+cpcssHeartbeat();
diff --git a/wp-content/plugins/wp-rocket/assets/js/wpr-cpcss.js b/wp-content/plugins/wp-rocket/assets/js/wpr-cpcss.js
new file mode 100644
index 00000000..020b873a
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/wpr-cpcss.js
@@ -0,0 +1,146 @@
+let checkCPCSSGenerationCall;
+let checkCPCSSMobileGenerationCall;
+let cpcsssGenerationPending = 0;
+let cpcsssMobileGenerationPending = 0;
+const rocketDeleteCPCSSbtn = document.getElementById( 'rocket-delete-post-cpss' );
+const rocketGenerateCPCSSbtn = document.getElementById( 'rocket-generate-post-cpss' );
+const rocketCPCSSGenerate = document.querySelectorAll( '.cpcss_generate' );
+const rocketCPCSSReGenerate = document.querySelectorAll( '.cpcss_regenerate' );
+
+rocketDeleteCPCSSbtn.addEventListener( 'click', e => {
+ e.preventDefault();
+ deleteCPCSS();
+} );
+
+rocketGenerateCPCSSbtn.addEventListener( 'click', e => {
+ e.preventDefault();
+ rocketGenerateCPCSSbtn.disabled = true;
+ checkCPCSSGeneration( null, false );
+ if ( rocket_cpcss.wprMobileCpcssEnabled ) {
+ checkCPCSSGeneration( null, true );
+ }
+} );
+
+const checkCPCSSGeneration = ( timeout = null, is_mobile = false ) => {
+ const spinner = rocketGenerateCPCSSbtn.querySelector( '.spinner' );
+ spinner.style.display = 'block';
+ spinner.style.visibility = 'visible';
+
+ const xhttp = new XMLHttpRequest();
+ xhttp.onload = () => {
+ if ( 200 !== xhttp.status ) {
+ return;
+ }
+
+ const cpcss_response = JSON.parse( xhttp.response );
+ if ( 200 !== cpcss_response.data.status ) {
+ stopCPCSSGeneration( spinner );
+ if ( ! is_mobile ) {
+ cpcssNotice( cpcss_response.message, 'error' );
+ }
+ rocketGenerateCPCSSbtn.disabled = false;
+ return;
+ }
+
+ if ( 200 === cpcss_response.data.status &&
+ 'cpcss_generation_pending' !== cpcss_response.code ) {
+ stopCPCSSGeneration( spinner, is_mobile );
+ if ( ! is_mobile ) {
+ cpcssNotice( cpcss_response.message, 'success' );
+ }
+
+ // Revert view to Regenerate.
+ rocketGenerateCPCSSbtn.querySelector( '.rocket-generate-post-cpss-btn-txt' ).innerHTML = rocket_cpcss.regenerate_btn;
+ rocketDeleteCPCSSbtn.style.display = 'block';
+ rocketGenerateCPCSSbtn.disabled = false;
+ rocketCPCSSGenerate.forEach( item => item.style.display = 'none' );
+ rocketCPCSSReGenerate.forEach( item => item.style.display = 'block' );
+ return;
+ }
+
+ if ( is_mobile ) {
+ cpcsssMobileGenerationPending++;
+ if ( cpcsssMobileGenerationPending > 10 ) {
+ stopCPCSSGeneration( spinner, is_mobile );
+ cpcsssMobileGenerationPending = 0;
+ checkCPCSSGeneration( true, true );
+ return;
+ }
+ checkCPCSSMobileGenerationCall = setTimeout( () => {
+ checkCPCSSGeneration( null, true );
+ }, 3000 );
+ } else {
+ cpcsssGenerationPending++;
+ if ( cpcsssGenerationPending > 10 ) {
+ stopCPCSSGeneration( spinner, is_mobile );
+ cpcsssGenerationPending = 0;
+ checkCPCSSGeneration( true, false );
+ return;
+ }
+
+ checkCPCSSGenerationCall = setTimeout( () => {
+ checkCPCSSGeneration( null, false );
+ }, 3000 );
+ }
+ };
+
+ xhttp.open( 'POST', rocket_cpcss.rest_url, true );
+ xhttp.setRequestHeader( 'Content-Type', 'application/json;charset=UTF-8' );
+ xhttp.setRequestHeader( 'X-WP-Nonce', rocket_cpcss.rest_nonce );
+ xhttp.send( JSON.stringify( { timeout: timeout, is_mobile: is_mobile } ) );
+}
+
+const stopCPCSSGeneration = ( spinner, is_mobile ) => {
+ spinner.style.display = 'none';
+ if ( is_mobile ) {
+ clearTimeout( checkCPCSSMobileGenerationCall );
+ } else {
+ clearTimeout( checkCPCSSGenerationCall );
+ }
+}
+
+const deleteCPCSS = () => {
+ rocketDeleteCPCSSbtn.disabled = true;
+
+ const xhttp = new XMLHttpRequest();
+ xhttp.onload = () => {
+ if ( 200 !== xhttp.status ) {
+ return;
+ }
+
+ rocketDeleteCPCSSbtn.disabled = false;
+ const cpcss_response = JSON.parse( xhttp.response );
+
+ if ( 200 !== cpcss_response.data.status ) {
+ cpcssNotice( cpcss_response.message, 'error' );
+ return;
+ }
+ cpcssNotice( cpcss_response.message, 'success' );
+
+ // Revert view to Generate.
+ rocketGenerateCPCSSbtn.querySelector( '.rocket-generate-post-cpss-btn-txt' ).innerHTML = rocket_cpcss.generate_btn;
+ rocketDeleteCPCSSbtn.style.display = 'none';
+ rocketCPCSSReGenerate.forEach( item => item.style.display = 'none' );
+ rocketCPCSSGenerate.forEach( item => item.style.display = 'block' );
+ };
+
+ xhttp.open( 'DELETE', rocket_cpcss.rest_url, true );
+ xhttp.setRequestHeader( 'Content-Type', 'application/json;charset=UTF-8' );
+ xhttp.setRequestHeader( 'X-WP-Nonce', rocket_cpcss.rest_nonce );
+ xhttp.send();
+}
+
+const cpcssNotice = ( msg, type ) => {
+ /* Add notice class */
+ const cpcssNotice = document.getElementById( 'cpcss_response_notice' );
+ cpcssNotice.innerHTML = '';
+ cpcssNotice.classList.remove( 'hidden', 'notice', 'is-warning', 'notice-error', 'notice-success', 'is-error', 'is-success' );
+ cpcssNotice.classList.add( 'notice', 'notice-' + type, 'is-' + type );
+
+ /* create paragraph element to hold message */
+ const p = document.createElement( 'p' );
+ p.appendChild( document.createTextNode( msg ) );
+
+ /* Add the whole message to notice div */
+ cpcssNotice.appendChild( p );
+}
diff --git a/wp-content/plugins/wp-rocket/assets/js/wpr-modal.js b/wp-content/plugins/wp-rocket/assets/js/wpr-modal.js
new file mode 100644
index 00000000..a1d947e0
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/assets/js/wpr-modal.js
@@ -0,0 +1,202 @@
+var $ = jQuery;
+$(document).ready(function(){
+
+ var $wprModal = $(".wpr-Modal");
+ if($wprModal){
+ new ModalWpr($wprModal);
+ }
+
+ /**
+ * AJAX Safe mode action button
+ */
+ $('#wpr-action-safe_mode').on('click', function(e) {
+ var button = $(this);
+ e.preventDefault();
+
+ $.post(
+ ajaxurl,
+ {
+ action: 'rocket_safe_mode',
+ nonce: rocket_ajax_data.nonce,
+ },
+ function(response) {
+ if ( true === response.success ) {
+ button.hide();
+ $('.show-if-safe-mode').show();
+ }
+ }
+ );
+ });
+});
+
+
+/*-----------------------------------------------*\
+ CLASS ModalWpr
+\*-----------------------------------------------*/
+/**
+ * Manages the display of deactivation modal box
+ *
+ * Public method :
+ open - Open the modal
+ close - Close the modal
+ change - Test if modal state change
+ *
+ */
+
+function ModalWpr(aElem) {
+
+ var refThis = this;
+
+ this.elem = aElem;
+ this.overlay = $('.wpr-Modal-overlay');
+ this.radio = $('input[name=reason]', aElem);
+ this.closer = $('.wpr-Modal-close, .wpr-Modal-cancel', aElem);
+ this.return = $('.wpr-Modal-return', aElem);
+ this.opener = $('.plugins [data-slug="wp-rocket"] .deactivate');
+ this.question = $('.wpr-Modal-question', aElem);
+ this.button = $('.button-primary', aElem);
+ this.title = $('.wpr-Modal-header h2', aElem);
+ this.textFields = $('input[type=text], textarea',aElem);
+ this.hiddenReason = $('#wpr-reason', aElem);
+ this.hiddenDetails = $('#wpr-details', aElem);
+ this.titleText = this.title.text();
+
+ // Open
+ this.opener.click(function() {
+ refThis.open();
+ return false;
+ });
+
+ // Close
+ this.closer.click(function() {
+ refThis.close();
+ return false;
+ });
+
+ aElem.bind('keyup', function(){
+ if(event.keyCode == 27){ // ECHAP
+ refThis.close();
+ return false;
+ }
+ });
+
+ // Back
+ this.return.click(function() {
+ refThis.returnToQuestion();
+ return false;
+ });
+
+ // Click on radio
+ this.radio.change(function(){
+ refThis.change($(this));
+ });
+
+ // Write text
+ this.textFields.keyup(function() {
+ refThis.hiddenDetails.val($(this).val());
+ if(refThis.hiddenDetails.val() != ''){
+ refThis.button.removeClass('wpr-isDisabled');
+ refThis.button.removeAttr("disabled");
+ }
+ else{
+ refThis.button.addClass('wpr-isDisabled');
+ refThis.button.attr("disabled", true);
+ }
+ });
+}
+
+
+/*
+* Change modal state
+*/
+ModalWpr.prototype.change = function(aElem) {
+
+ var id = aElem.attr('id');
+ var refThis = this;
+
+ // Reset values
+ this.hiddenReason.val(aElem.val());
+ this.hiddenDetails.val('');
+ this.textFields.val('');
+
+ $('.wpr-Modal-fieldHidden').removeClass('wpr-isOpen');
+ $('.wpr-Modal-hidden').removeClass('wpr-isOpen');
+ this.button.removeClass('wpr-isDisabled');
+ this.button.removeAttr("disabled");
+
+ switch(id){
+ case 'reason-temporary':
+ // Nothing to do
+ break;
+
+ case 'reason-broke':
+ case 'reason-score':
+ case 'reason-loading':
+ case 'reason-complicated':
+ var $panel = $('#' + id + '-panel');
+ refThis.question.removeClass('wpr-isOpen');
+ refThis.return.addClass('wpr-isOpen');
+ $panel.addClass('wpr-isOpen');
+
+ var titleText = $panel.find('h3').text();
+ this.title.text(titleText);
+ break;
+
+ case 'reason-host':
+ case 'reason-other':
+ var field = aElem.siblings('.wpr-Modal-fieldHidden');
+ field.addClass('wpr-isOpen');
+ field.find('input, textarea').focus();
+ refThis.button.addClass('wpr-isDisabled');
+ refThis.button.attr("disabled", true);
+ break;
+ }
+};
+
+
+
+/*
+* Return to the question
+*/
+ModalWpr.prototype.returnToQuestion = function() {
+
+ $('.wpr-Modal-fieldHidden').removeClass('wpr-isOpen');
+ $('.wpr-Modal-hidden').removeClass('wpr-isOpen');
+ this.question.addClass('wpr-isOpen');
+ this.return.removeClass('wpr-isOpen');
+ this.title.text(this.titleText);
+
+ // Reset values
+ this.hiddenReason.val('');
+ this.hiddenDetails.val('');
+
+ this.radio.attr('checked', false);
+ this.button.addClass('wpr-isDisabled');
+ this.button.attr("disabled", true);
+
+};
+
+
+/*
+* Open modal
+*/
+ModalWpr.prototype.open = function() {
+
+ this.elem.css('display','block');
+ this.overlay.css('display','block');
+
+ // Reset current tab wp-rocket
+ localStorage.setItem('wpr-hash', '');
+};
+
+
+/*
+* Close modal
+*/
+ModalWpr.prototype.close = function() {
+
+ this.returnToQuestion();
+ this.elem.css('display','none');
+ this.overlay.css('display','none');
+
+};
diff --git a/wp-content/plugins/wp-rocket/composer.json b/wp-content/plugins/wp-rocket/composer.json
new file mode 100644
index 00000000..1a504734
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/composer.json
@@ -0,0 +1,172 @@
+{
+ "name": "wp-media/wp-rocket",
+ "description": "Performance optimization plugin for WordPress",
+ "keywords": [
+ "wordpress",
+ "cache",
+ "minification",
+ "lazyload"
+ ],
+ "homepage": "https://wp-rocket.me",
+ "license": "GPL-2.0-or-later",
+ "authors": [
+ {
+ "name": "WP Media",
+ "email": "contact@wp-media.me",
+ "homepage": "https://wp-media.me"
+ }
+ ],
+ "type": "wordpress-plugin",
+ "config": {
+ "sort-packages": true,
+ "preferred-install": {
+ "wp-media/phpunit": "source"
+ }
+ },
+ "support": {
+ "issues": "https://github.com/wp-media/wp-rocket/issues",
+ "source": "https://github.com/wp-media/wp-rocket"
+ },
+ "repositories":[
+ {
+ "type": "composer",
+ "url": "https://wpackagist.org"
+ }
+ ],
+ "require": {
+ "php": ">=7.0",
+ "composer/installers": "~1.0",
+ "monolog/monolog": "^1.0",
+ "psr/container": "^1.0"
+ },
+ "require-dev": {
+ "php": "^7",
+ "brain/monkey": "^2.0",
+ "coenjacobs/mozart": "^0.7",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+ "mikey179/vfsstream": "^1.6",
+ "mobiledetect/mobiledetectlib": "^2.8",
+ "mnsami/composer-custom-directory-installer": "^2.0",
+ "phpcompatibility/phpcompatibility-wp": "^2.0",
+ "phpstan/phpstan": "^0.12.3",
+ "phpunit/phpunit": "^7",
+ "roave/security-advisories": "dev-master",
+ "szepeviktor/phpstan-wordpress": "^0.5.0",
+ "woocommerce/woocommerce": "^3.9",
+ "wp-coding-standards/wpcs": "^2",
+ "wp-media/background-processing": "^1.3",
+ "wp-media/cloudflare": "^1.0",
+ "wp-media/module-rocketcdn": "^1.0",
+ "wp-media/module-varnish": "^1.0",
+ "wp-media/rocket-lazyload-common": "^2",
+ "wp-media/phpunit": "^1.0",
+ "wp-media/wp-imagify-partner": "^1.0",
+ "wpackagist-plugin/amp": "^1.1.4",
+ "wpackagist-plugin/hummingbird-performance": "2.0.1",
+ "wpackagist-plugin/pdf-embedder": "^4.6",
+ "wpackagist-plugin/simple-custom-css": "^4.0.3",
+ "wpackagist-plugin/spinupwp": "^1.1",
+ "wpackagist-plugin/wp-smushit": "^3.0"
+ },
+ "autoload": {
+ "classmap": [
+ "inc/classes",
+ "inc/vendors/classes",
+ "inc/deprecated"
+ ],
+ "exclude-from-classmap": [
+ "inc/vendors/classes/class-rocket-mobile-detect.php",
+ "inc/classes/class-wp-rocket-requirements-check.php"
+ ],
+ "psr-4": {
+ "WP_Rocket\\": "inc/",
+ "WPMedia\\Cloudflare\\": "inc/Addon/Cloudflare/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "WP_Rocket\\Tests\\": "tests/"
+ }
+ },
+ "extra": {
+ "installer-paths": {
+ "vendor/{$vendor}/{$name}/": ["type:wordpress-plugin"],
+ "./inc/Addon/Cloudflare/": ["wp-media/cloudflare"],
+ "./inc/Addon/Varnish/": ["wp-media/module-varnish"],
+ "./inc/Engine/CDN/RocketCDN/": ["wp-media/module-rocketcdn"]
+ },
+ "mozart": {
+ "dep_namespace": "WP_Rocket\\Dependencies\\",
+ "dep_directory": "/inc/Dependencies/",
+ "classmap_directory": "/inc/classes/dependencies/",
+ "classmap_prefix": "WP_Rocket_",
+ "packages": [
+ "mobiledetect/mobiledetectlib",
+ "wp-media/background-processing",
+ "wp-media/rocket-lazyload-common",
+ "wp-media/wp-imagify-partner"
+ ]
+ }
+ },
+ "scripts": {
+ "test-unit": "\"vendor/bin/phpunit\" --testsuite unit --colors=always --configuration tests/Unit/phpunit.xml.dist",
+ "test-integration": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --exclude-group AdminOnly,BeaverBuilder,Elementor,Hummingbird,WithSmush,WithWoo,WithAmp,WithAmpAndCloudflare,WithSCCSS,Cloudways,Dreampress,DoCloudflare,Multisite,WPEngine,SpinUpWP,WordPressCom,O2Switch,PDFEmbedder,PDFEmbedderPremium,PDFEmbedderSecure",
+ "test-integration-adminonly": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group AdminOnly",
+ "test-integration-bb": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group BeaverBuilder",
+ "test-integration-cloudflare": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group DoCloudflare",
+ "test-integration-cloudways": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Cloudways",
+ "test-integration-elementor": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Elementor",
+ "test-integration-hummingbird": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Hummingbird",
+ "test-integration-multisite": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Multisite",
+ "test-integration-withsmush": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithSmush",
+ "test-integration-withamp": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithAmp",
+ "test-integration-withampcloudflare": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithAmpAndCloudflare",
+ "test-integration-withsccss": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithSCCSS",
+ "test-integration-withwoo": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WithWoo",
+ "test-integration-wpengine": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WPEngine",
+ "test-integration-spinupwp": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group SpinUpWP",
+ "test-integration-wpcom": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group WordPressCom",
+ "test-integration-pdfembedder": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedder",
+ "test-integration-pdfembedderpremium": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedderPremium",
+ "test-integration-pdfembeddersecure": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group PDFEmbedderSecure",
+ "test-integration-o2switch": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group O2Switch",
+ "test-integration-dreampress": "\"vendor/bin/phpunit\" --testsuite integration --colors=always --configuration tests/Integration/phpunit.xml.dist --group Dreampress",
+ "run-tests": [
+ "@test-unit",
+ "@test-integration",
+ "@test-integration-adminonly",
+ "@test-integration-cloudflare",
+ "@test-integration-bb",
+ "@test-integration-elementor",
+ "@test-integration-hummingbird",
+ "@test-integration-withamp",
+ "@test-integration-withampcloudflare",
+ "@test-integration-withsccss",
+ "@test-integration-withsmush",
+ "@test-integration-withwoo",
+ "@test-integration-pdfembedder",
+ "@test-integration-pdfembedderpremium",
+ "@test-integration-pdfembeddersecure",
+ "@test-integration-multisite",
+ "@test-integration-cloudways",
+ "@test-integration-wpengine",
+ "@test-integration-spinupwp",
+ "@test-integration-wpcom",
+ "@test-integration-o2switch",
+ "@test-integration-dreampress"
+ ],
+ "run-stan":"vendor/bin/phpstan analyze --memory-limit=2G --no-progress",
+ "install-codestandards": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin::run",
+ "phpcs": "phpcs --basepath=.",
+ "phpcs-changed": "./bin/phpcs-changed.sh",
+ "phpcs:fix": "phpcbf",
+ "post-install-cmd": [
+ "\"vendor/bin/mozart\" compose",
+ "composer dump-autoload"
+ ],
+ "post-update-cmd": [
+ "\"vendor/bin/mozart\" compose",
+ "composer dump-autoload"
+ ]
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/composer.lock b/wp-content/plugins/wp-rocket/composer.lock
new file mode 100644
index 00000000..627ea84b
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/composer.lock
@@ -0,0 +1,4631 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "79d90de05c880c9879bb38a8cb810e88",
+ "packages": [
+ {
+ "name": "composer/installers",
+ "version": "v1.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/installers.git",
+ "reference": "141b272484481432cda342727a427dc1e206bfa0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/installers/zipball/141b272484481432cda342727a427dc1e206bfa0",
+ "reference": "141b272484481432cda342727a427dc1e206bfa0",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0"
+ },
+ "replace": {
+ "roundcube/plugin-installer": "*",
+ "shama/baton": "*"
+ },
+ "require-dev": {
+ "composer/composer": "1.0.*@dev",
+ "phpunit/phpunit": "^4.8.36"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Composer\\Installers\\Plugin",
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Installers\\": "src/Composer/Installers"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kyle Robinson Young",
+ "email": "kyle@dontkry.com",
+ "homepage": "https://github.com/shama"
+ }
+ ],
+ "description": "A multi-framework Composer library installer",
+ "homepage": "https://composer.github.io/installers/",
+ "keywords": [
+ "Craft",
+ "Dolibarr",
+ "Eliasis",
+ "Hurad",
+ "ImageCMS",
+ "Kanboard",
+ "Lan Management System",
+ "MODX Evo",
+ "Mautic",
+ "Maya",
+ "OXID",
+ "Plentymarkets",
+ "Porto",
+ "RadPHP",
+ "SMF",
+ "Thelia",
+ "Whmcs",
+ "WolfCMS",
+ "agl",
+ "aimeos",
+ "annotatecms",
+ "attogram",
+ "bitrix",
+ "cakephp",
+ "chef",
+ "cockpit",
+ "codeigniter",
+ "concrete5",
+ "croogo",
+ "dokuwiki",
+ "drupal",
+ "eZ Platform",
+ "elgg",
+ "expressionengine",
+ "fuelphp",
+ "grav",
+ "installer",
+ "itop",
+ "joomla",
+ "known",
+ "kohana",
+ "laravel",
+ "lavalite",
+ "lithium",
+ "magento",
+ "majima",
+ "mako",
+ "mediawiki",
+ "modulework",
+ "modx",
+ "moodle",
+ "osclass",
+ "phpbb",
+ "piwik",
+ "ppi",
+ "puppet",
+ "pxcms",
+ "reindex",
+ "roundcube",
+ "shopware",
+ "silverstripe",
+ "sydes",
+ "symfony",
+ "typo3",
+ "wordpress",
+ "yawik",
+ "zend",
+ "zikula"
+ ],
+ "time": "2019-08-12T15:00:31+00:00"
+ },
+ {
+ "name": "monolog/monolog",
+ "version": "1.26.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "2209ddd84e7ef1256b7af205d0717fb62cfc9c33"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/2209ddd84e7ef1256b7af205d0717fb62cfc9c33",
+ "reference": "2209ddd84e7ef1256b7af205d0717fb62cfc9c33",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "psr/log": "~1.0"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "graylog2/gelf-php": "~1.0",
+ "php-amqplib/php-amqplib": "~2.4",
+ "php-console/php-console": "^3.1.3",
+ "phpstan/phpstan": "^0.12.59",
+ "phpunit/phpunit": "~4.5",
+ "ruflin/elastica": ">=0.90 <3.0",
+ "sentry/sentry": "^0.13",
+ "swiftmailer/swiftmailer": "^5.3|^6.0"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-mongo": "Allow sending log messages to a MongoDB server",
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "php-console/php-console": "Allow sending log messages to Google Chrome",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
+ "sentry/sentry": "Allow sending log messages to a Sentry server"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Monolog\\": "src/Monolog"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+ "homepage": "http://github.com/Seldaek/monolog",
+ "keywords": [
+ "log",
+ "logging",
+ "psr-3"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/Seldaek",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-12-14T12:56:38+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "time": "2017-02-14T16:28:37+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
+ "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "Psr/Log/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "time": "2020-03-23T09:12:05+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "antecedent/patchwork",
+ "version": "2.1.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antecedent/patchwork.git",
+ "reference": "b98e046dd4c0acc34a0846604f06f6111654d9ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antecedent/patchwork/zipball/b98e046dd4c0acc34a0846604f06f6111654d9ea",
+ "reference": "b98e046dd4c0acc34a0846604f06f6111654d9ea",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": ">=4"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignas Rudaitis",
+ "email": "ignas.rudaitis@gmail.com"
+ }
+ ],
+ "description": "Method redefinition (monkey-patching) functionality for PHP.",
+ "homepage": "http://patchwork2.org/",
+ "keywords": [
+ "aop",
+ "aspect",
+ "interception",
+ "monkeypatching",
+ "redefinition",
+ "runkit",
+ "testing"
+ ],
+ "time": "2019-12-22T17:52:09+00:00"
+ },
+ {
+ "name": "automattic/jetpack-autoloader",
+ "version": "v1.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Automattic/jetpack-autoloader.git",
+ "reference": "301c2fbcf070d4f0147753447616b6e982bda09e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/301c2fbcf070d4f0147753447616b6e982bda09e",
+ "reference": "301c2fbcf070d4f0147753447616b6e982bda09e",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Automattic\\Jetpack\\Autoloader\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "Creates a custom autoloader for a plugin or theme.",
+ "time": "2019-09-24T06:39:29+00:00"
+ },
+ {
+ "name": "brain/monkey",
+ "version": "2.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Brain-WP/BrainMonkey.git",
+ "reference": "7042140000b4b18034c0c0010d86274a00f25442"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Brain-WP/BrainMonkey/zipball/7042140000b4b18034c0c0010d86274a00f25442",
+ "reference": "7042140000b4b18034c0c0010d86274a00f25442",
+ "shasum": ""
+ },
+ "require": {
+ "antecedent/patchwork": "^2.0",
+ "mockery/mockery": ">=0.9 <2",
+ "php": ">=5.6.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || ^0.7",
+ "phpcompatibility/php-compatibility": "^9.3.0",
+ "phpunit/phpunit": "^5.7.9 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-version/1": "1.x-dev",
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Brain\\Monkey\\": "src/"
+ },
+ "files": [
+ "inc/api.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Giuseppe Mazzapica",
+ "email": "giuseppe.mazzapica@gmail.com",
+ "homepage": "https://gmazzap.me",
+ "role": "Developer"
+ }
+ ],
+ "description": "Mocking utility for PHP functions and WordPress plugin API",
+ "keywords": [
+ "Monkey Patching",
+ "interception",
+ "mock",
+ "mock functions",
+ "mockery",
+ "patchwork",
+ "redefinition",
+ "runkit",
+ "test",
+ "testing"
+ ],
+ "time": "2020-10-13T17:56:14+00:00"
+ },
+ {
+ "name": "coenjacobs/mozart",
+ "version": "0.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/coenjacobs/mozart.git",
+ "reference": "dbcdeb992d20d9c8914eef090f9a0d684bb1102c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/coenjacobs/mozart/zipball/dbcdeb992d20d9c8914eef090f9a0d684bb1102c",
+ "reference": "dbcdeb992d20d9c8914eef090f9a0d684bb1102c",
+ "shasum": ""
+ },
+ "require": {
+ "league/flysystem": "^1.0",
+ "php": "^7.3|^8.0",
+ "symfony/console": "^4|^5",
+ "symfony/finder": "^4|^5"
+ },
+ "require-dev": {
+ "mheap/phpunit-github-actions-printer": "^1.4",
+ "phpunit/phpunit": "^8.5",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^4.4"
+ },
+ "bin": [
+ "bin/mozart"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "CoenJacobs\\Mozart\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Coen Jacobs",
+ "email": "coenjacobs@gmail.com"
+ }
+ ],
+ "description": "Composes all dependencies as a package inside a WordPress plugin",
+ "funding": [
+ {
+ "url": "https://github.com/coenjacobs",
+ "type": "github"
+ }
+ ],
+ "time": "2021-02-02T21:37:03+00:00"
+ },
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v0.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
+ "reference": "fe390591e0241955f22eb9ba327d137e501c771c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c",
+ "reference": "fe390591e0241955f22eb9ba327d137e501c771c",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0",
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "sensiolabs/security-checker": "^4.1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "homepage": "http://www.dealerdirect.com",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "time": "2020-12-07T18:04:37+00:00"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^8.0",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "https://ocramius.github.io/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-10T18:47:58+00:00"
+ },
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
+ "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3|^7.0|^8.0"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "^1.4 || ^2.0",
+ "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "time": "2020-07-09T08:09:16+00:00"
+ },
+ {
+ "name": "league/flysystem",
+ "version": "1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem.git",
+ "reference": "9be3b16c877d477357c015cec057548cf9b2a14a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a",
+ "reference": "9be3b16c877d477357c015cec057548cf9b2a14a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "league/mime-type-detection": "^1.3",
+ "php": "^7.2.5 || ^8.0"
+ },
+ "conflict": {
+ "league/flysystem-sftp": "<1.0.6"
+ },
+ "require-dev": {
+ "phpspec/prophecy": "^1.11.1",
+ "phpunit/phpunit": "^8.5.8"
+ },
+ "suggest": {
+ "ext-fileinfo": "Required for MimeType",
+ "ext-ftp": "Allows you to use FTP server storage",
+ "ext-openssl": "Allows you to use FTPS server storage",
+ "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+ "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+ "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+ "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+ "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+ "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+ "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+ "league/flysystem-webdav": "Allows you to use WebDAV storage",
+ "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+ "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+ "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "description": "Filesystem abstraction: Many filesystems, one API.",
+ "keywords": [
+ "Cloud Files",
+ "WebDAV",
+ "abstraction",
+ "aws",
+ "cloud",
+ "copy.com",
+ "dropbox",
+ "file systems",
+ "files",
+ "filesystem",
+ "filesystems",
+ "ftp",
+ "rackspace",
+ "remote",
+ "s3",
+ "sftp",
+ "storage"
+ ],
+ "funding": [
+ {
+ "url": "https://offset.earth/frankdejonge",
+ "type": "other"
+ }
+ ],
+ "time": "2020-08-23T07:39:11+00:00"
+ },
+ {
+ "name": "league/mime-type-detection",
+ "version": "1.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/mime-type-detection.git",
+ "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
+ "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
+ "shasum": ""
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.18",
+ "phpstan/phpstan": "^0.12.68",
+ "phpunit/phpunit": "^8.5.8 || ^9.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "League\\MimeTypeDetection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frankdejonge.nl"
+ }
+ ],
+ "description": "Mime-type detection for Flysystem",
+ "funding": [
+ {
+ "url": "https://github.com/frankdejonge",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/league/flysystem",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-18T20:58:21+00:00"
+ },
+ {
+ "name": "maxmind-db/reader",
+ "version": "v1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
+ "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/febd4920bf17c1da84cef58e56a8227dfb37fbe4",
+ "reference": "febd4920bf17c1da84cef58e56a8227dfb37fbe4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "conflict": {
+ "ext-maxminddb": "<1.6.0,>=2.0.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "2.*",
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpcov": "^3.0",
+ "phpunit/phpunit": "5.*",
+ "squizlabs/php_codesniffer": "3.*"
+ },
+ "suggest": {
+ "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
+ "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
+ "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MaxMind\\Db\\": "src/MaxMind/Db"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Gregory J. Oschwald",
+ "email": "goschwald@maxmind.com",
+ "homepage": "https://www.maxmind.com/"
+ }
+ ],
+ "description": "MaxMind DB Reader API",
+ "homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
+ "keywords": [
+ "database",
+ "geoip",
+ "geoip2",
+ "geolocation",
+ "maxmind"
+ ],
+ "time": "2019-12-19T22:59:03+00:00"
+ },
+ {
+ "name": "mikey179/vfsstream",
+ "version": "v1.6.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bovigo/vfsStream.git",
+ "reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/231c73783ebb7dd9ec77916c10037eff5a2b6efe",
+ "reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.5|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.6.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "org\\bovigo\\vfs\\": "src/main/php"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Frank Kleine",
+ "homepage": "http://frankkleine.de/",
+ "role": "Developer"
+ }
+ ],
+ "description": "Virtual file system to mock the real file system in unit tests.",
+ "homepage": "http://vfs.bovigo.org/",
+ "time": "2019-10-30T15:31:00+00:00"
+ },
+ {
+ "name": "mnsami/composer-custom-directory-installer",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mnsami/composer-custom-directory-installer.git",
+ "reference": "85f66323978d0b1cb0e6acc7f69b3e7b912f82d9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mnsami/composer-custom-directory-installer/zipball/85f66323978d0b1cb0e6acc7f69b3e7b912f82d9",
+ "reference": "85f66323978d0b1cb0e6acc7f69b3e7b912f82d9",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0",
+ "php": ">=5.3"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": [
+ "Composer\\CustomDirectoryInstaller\\LibraryPlugin",
+ "Composer\\CustomDirectoryInstaller\\PearPlugin",
+ "Composer\\CustomDirectoryInstaller\\PluginPlugin"
+ ],
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Composer\\CustomDirectoryInstaller": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mina Nabil Sami",
+ "email": "mina.nsami@gmail.com"
+ }
+ ],
+ "description": "A composer plugin, to help install packages of different types in custom paths.",
+ "keywords": [
+ "composer",
+ "composer-installer",
+ "composer-plugin"
+ ],
+ "time": "2020-08-18T11:00:11+00:00"
+ },
+ {
+ "name": "mobiledetect/mobiledetectlib",
+ "version": "2.8.36",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/serbanghita/Mobile-Detect.git",
+ "reference": "e55c155f4d1ab299a0cfca8ec29a0154918c9e3d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/e55c155f4d1ab299a0cfca8ec29a0154918c9e3d",
+ "reference": "e55c155f4d1ab299a0cfca8ec29a0154918c9e3d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.0.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8.35||~5.7"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "Mobile_Detect.php"
+ ],
+ "psr-0": {
+ "Detection": "namespaced/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Serban Ghita",
+ "email": "serbanghita@gmail.com",
+ "homepage": "http://mobiledetect.net",
+ "role": "Developer"
+ }
+ ],
+ "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.",
+ "homepage": "https://github.com/serbanghita/Mobile-Detect",
+ "keywords": [
+ "detect mobile devices",
+ "mobile",
+ "mobile detect",
+ "mobile detector",
+ "php mobile detect"
+ ],
+ "time": "2021-02-13T14:35:52+00:00"
+ },
+ {
+ "name": "mockery/mockery",
+ "version": "1.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mockery/mockery.git",
+ "reference": "60fa2f67f6e4d3634bb4a45ff3171fa52215800d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/60fa2f67f6e4d3634bb4a45ff3171fa52215800d",
+ "reference": "60fa2f67f6e4d3634bb4a45ff3171fa52215800d",
+ "shasum": ""
+ },
+ "require": {
+ "hamcrest/hamcrest-php": "^2.0.1",
+ "lib-pcre": ">=7.0",
+ "php": ">=5.6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7.10|^6.5|^7.5|^8.5|^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Mockery": "library/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Pádraic Brady",
+ "email": "padraic.brady@gmail.com",
+ "homepage": "http://blog.astrumfutura.com"
+ },
+ {
+ "name": "Dave Marshall",
+ "email": "dave.marshall@atstsolutions.co.uk",
+ "homepage": "http://davedevelopment.co.uk"
+ }
+ ],
+ "description": "Mockery is a simple yet flexible PHP mock object framework",
+ "homepage": "https://github.com/mockery/mockery",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "library",
+ "mock",
+ "mock objects",
+ "mockery",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "time": "2020-08-11T18:10:21+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.10.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "replace": {
+ "myclabs/deep-copy": "self.version"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ },
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-13T09:40:50+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+ "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "phar-io/version": "^2.0",
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "time": "2018-07-08T19:23:20+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+ "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "time": "2018-07-08T19:19:57+00:00"
+ },
+ {
+ "name": "php-stubs/wordpress-stubs",
+ "version": "v5.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-stubs/wordpress-stubs.git",
+ "reference": "ed446cce304cd49f13900274b3ed60d1b526297e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/ed446cce304cd49f13900274b3ed60d1b526297e",
+ "reference": "ed446cce304cd49f13900274b3ed60d1b526297e",
+ "shasum": ""
+ },
+ "replace": {
+ "giacocorsiglia/wordpress-stubs": "*"
+ },
+ "require-dev": {
+ "giacocorsiglia/stubs-generator": "^0.5.0",
+ "php": "~7.1"
+ },
+ "suggest": {
+ "paragonie/sodium_compat": "Pure PHP implementation of libsodium",
+ "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+ "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "WordPress function and class declaration stubs for static analysis.",
+ "homepage": "https://github.com/php-stubs/wordpress-stubs",
+ "keywords": [
+ "PHPStan",
+ "static analysis",
+ "wordpress"
+ ],
+ "time": "2020-12-09T00:38:16+00:00"
+ },
+ {
+ "name": "phpcompatibility/php-compatibility",
+ "version": "9.3.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+ },
+ "conflict": {
+ "squizlabs/php_codesniffer": "2.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "homepage": "https://github.com/wimg",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+ }
+ ],
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards"
+ ],
+ "time": "2019-12-27T09:44:58+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-paragonie",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
+ "reference": "ddabec839cc003651f2ce695c938686d1086cf43"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/ddabec839cc003651f2ce695c938686d1086cf43",
+ "reference": "ddabec839cc003651f2ce695c938686d1086cf43",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7",
+ "paragonie/random_compat": "dev-master",
+ "paragonie/sodium_compat": "dev-master"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "paragonie",
+ "phpcs",
+ "polyfill",
+ "standards"
+ ],
+ "time": "2021-02-15T10:24:51+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-wp",
+ "version": "2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
+ "reference": "b7dc0cd7a8f767ccac5e7637550ea1c50a67b09e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b7dc0cd7a8f767ccac5e7637550ea1c50a67b09e",
+ "reference": "b7dc0cd7a8f767ccac5e7637550ea1c50a67b09e",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpcompatibility/phpcompatibility-paragonie": "^1.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards",
+ "wordpress"
+ ],
+ "time": "2021-02-15T12:58:46+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.3",
+ "webmozart/assert": "^1.9.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "account@ijaap.nl"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2020-09-03T19:13:55+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "time": "2020-09-17T18:55:26+00:00"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "1.12.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "245710e971a030f42e08f4912863805570f23d39"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39",
+ "reference": "245710e971a030f42e08f4912863805570f23d39",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.2",
+ "php": "^7.2 || ~8.0, <8.1",
+ "phpdocumentor/reflection-docblock": "^5.2",
+ "sebastian/comparator": "^3.0 || ^4.0",
+ "sebastian/recursion-context": "^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^6.0",
+ "phpunit/phpunit": "^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Prophecy\\": "src/Prophecy"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2020-12-19T10:15:11+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "0.12.76",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "7aaaf9a759a29795e8f46d48041af1c1f1b23d38"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7aaaf9a759a29795e8f46d48041af1c1f1b23d38",
+ "reference": "7aaaf9a759a29795e8f46d48041af1c1f1b23d38",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.12-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/phpstan",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-02-13T11:47:44+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "6.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
+ "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.1",
+ "phpunit/php-file-iterator": "^2.0",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-token-stream": "^3.0",
+ "sebastian/code-unit-reverse-lookup": "^1.0.1",
+ "sebastian/environment": "^3.1 || ^4.0",
+ "sebastian/version": "^2.0.1",
+ "theseer/tokenizer": "^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0"
+ },
+ "suggest": {
+ "ext-xdebug": "^2.6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2018-10-31T16:06:48+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357",
+ "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T08:25:21+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2015-06-21T13:50:34+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "2.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662",
+ "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T08:20:02+00:00"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "3.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "472b687829041c24b25f475e14c2f38a09edf1c2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/472b687829041c24b25f475e14c2f38a09edf1c2",
+ "reference": "472b687829041c24b25f475e14c2f38a09edf1c2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "abandoned": true,
+ "time": "2020-11-30T08:38:46+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "7.5.20",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "9467db479d1b0487c99733bb1e7944d32deded2c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c",
+ "reference": "9467db479d1b0487c99733bb1e7944d32deded2c",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.1",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "myclabs/deep-copy": "^1.7",
+ "phar-io/manifest": "^1.0.2",
+ "phar-io/version": "^2.0",
+ "php": "^7.1",
+ "phpspec/prophecy": "^1.7",
+ "phpunit/php-code-coverage": "^6.0.7",
+ "phpunit/php-file-iterator": "^2.0.1",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-timer": "^2.1",
+ "sebastian/comparator": "^3.0",
+ "sebastian/diff": "^3.0",
+ "sebastian/environment": "^4.0",
+ "sebastian/exporter": "^3.1",
+ "sebastian/global-state": "^2.0",
+ "sebastian/object-enumerator": "^3.0.3",
+ "sebastian/resource-operations": "^2.0",
+ "sebastian/version": "^2.0.1"
+ },
+ "conflict": {
+ "phpunit/phpunit-mock-objects": "*"
+ },
+ "require-dev": {
+ "ext-pdo": "*"
+ },
+ "suggest": {
+ "ext-soap": "*",
+ "ext-xdebug": "*",
+ "phpunit/php-invoker": "^2.0"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "7.5-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2020-01-08T08:45:45+00:00"
+ },
+ {
+ "name": "roave/security-advisories",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Roave/SecurityAdvisories.git",
+ "reference": "5f40d4d577a71466f9723122251b46bdaf634709"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5f40d4d577a71466f9723122251b46bdaf634709",
+ "reference": "5f40d4d577a71466f9723122251b46bdaf634709",
+ "shasum": ""
+ },
+ "conflict": {
+ "3f/pygmentize": "<1.2",
+ "adodb/adodb-php": "<5.20.12",
+ "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1",
+ "amphp/artax": "<1.0.6|>=2,<2.0.6",
+ "amphp/http": "<1.0.1",
+ "amphp/http-client": ">=4,<4.4",
+ "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6",
+ "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99",
+ "aws/aws-sdk-php": ">=3,<3.2.1",
+ "bagisto/bagisto": "<0.1.5",
+ "barrelstrength/sprout-base-email": "<1.2.7",
+ "barrelstrength/sprout-forms": "<3.9",
+ "baserproject/basercms": ">=4,<=4.3.6|>=4.4,<4.4.1",
+ "bolt/bolt": "<3.7.1",
+ "brightlocal/phpwhois": "<=4.2.5",
+ "buddypress/buddypress": "<5.1.2",
+ "bugsnag/bugsnag-laravel": ">=2,<2.0.2",
+ "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.5.18|>=3.6,<3.6.15|>=3.7,<3.7.7",
+ "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4",
+ "cartalyst/sentry": "<=2.1.6",
+ "centreon/centreon": "<18.10.8|>=19,<19.4.5",
+ "cesnet/simplesamlphp-module-proxystatistics": "<3.1",
+ "codeigniter/framework": "<=3.0.6",
+ "composer/composer": "<=1-alpha.11",
+ "contao-components/mediaelement": ">=2.14.2,<2.21.1",
+ "contao/core": ">=2,<3.5.39",
+ "contao/core-bundle": ">=4,<4.4.52|>=4.5,<4.9.6|= 4.10.0",
+ "contao/listing-bundle": ">=4,<4.4.8",
+ "datadog/dd-trace": ">=0.30,<0.30.2",
+ "david-garcia/phpwhois": "<=4.3.1",
+ "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1",
+ "doctrine/annotations": ">=1,<1.2.7",
+ "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2",
+ "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1",
+ "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2",
+ "doctrine/doctrine-bundle": "<1.5.2",
+ "doctrine/doctrine-module": "<=0.7.1",
+ "doctrine/mongodb-odm": ">=1,<1.0.2",
+ "doctrine/mongodb-odm-bundle": ">=2,<3.0.1",
+ "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1",
+ "dolibarr/dolibarr": "<11.0.4",
+ "dompdf/dompdf": ">=0.6,<0.6.2",
+ "drupal/core": ">=7,<7.74|>=8,<8.8.11|>=8.9,<8.9.9|>=9,<9.0.8",
+ "drupal/drupal": ">=7,<7.74|>=8,<8.8.11|>=8.9,<8.9.9|>=9,<9.0.8",
+ "endroid/qr-code-bundle": "<3.4.2",
+ "enshrined/svg-sanitize": "<0.13.1",
+ "erusev/parsedown": "<1.7.2",
+ "ezsystems/demobundle": ">=5.4,<5.4.6.1",
+ "ezsystems/ez-support-tools": ">=2.2,<2.2.3",
+ "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1",
+ "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1",
+ "ezsystems/ezplatform": ">=1.7,<1.7.9.1|>=1.13,<1.13.5.1|>=2.5,<2.5.4",
+ "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6",
+ "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1",
+ "ezsystems/ezplatform-kernel": ">=1,<1.0.2.1",
+ "ezsystems/ezplatform-user": ">=1,<1.0.1",
+ "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.2|>=6,<6.7.9.1|>=6.8,<6.13.6.3|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.7.1",
+ "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.14.2|>=2011,<2017.12.7.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3|>=2019.3,<2019.3.5.1",
+ "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3",
+ "ezsystems/repository-forms": ">=2.3,<2.3.2.1",
+ "ezyang/htmlpurifier": "<4.1.1",
+ "firebase/php-jwt": "<2",
+ "flarum/sticky": ">=0.1-beta.14,<=0.1-beta.15",
+ "flarum/tags": "<=0.1-beta.13",
+ "fooman/tcpdf": "<6.2.22",
+ "fossar/tcpdf-parser": "<6.2.22",
+ "friendsofsymfony/oauth2-php": "<1.3",
+ "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2",
+ "friendsofsymfony/user-bundle": ">=1.2,<1.3.5",
+ "friendsoftypo3/mediace": ">=7.6.2,<7.6.5",
+ "fuel/core": "<1.8.1",
+ "getgrav/grav": "<1.7-beta.8",
+ "getkirby/cms": ">=3,<3.4.5",
+ "getkirby/panel": "<2.5.14",
+ "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3",
+ "gree/jose": "<=2.2",
+ "gregwar/rst": "<1.0.3",
+ "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1",
+ "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10",
+ "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4",
+ "illuminate/database": "<6.20.14|>=7,<7.30.4|>=8,<8.24",
+ "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15",
+ "illuminate/view": ">=7,<7.1.2",
+ "ivankristianto/phpwhois": "<=4.3",
+ "james-heinrich/getid3": "<1.9.9",
+ "joomla/session": "<1.3.1",
+ "jsmitty12/phpwhois": "<5.1",
+ "kazist/phpwhois": "<=4.2.6",
+ "kitodo/presentation": "<3.1.2",
+ "kreait/firebase-php": ">=3.2,<3.8.1",
+ "la-haute-societe/tcpdf": "<6.2.22",
+ "laravel/framework": "<6.20.14|>=7,<7.30.4|>=8,<8.24",
+ "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10",
+ "league/commonmark": "<0.18.3",
+ "librenms/librenms": "<1.53",
+ "livewire/livewire": ">2.2.4,<2.2.6",
+ "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3",
+ "magento/magento1ce": "<1.9.4.3",
+ "magento/magento1ee": ">=1,<1.14.4.3",
+ "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2",
+ "marcwillmann/turn": "<0.3.3",
+ "mautic/core": "<2.16.5|>=3,<3.2.4|= 2.13.1",
+ "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35",
+ "mittwald/typo3_forum": "<1.2.1",
+ "monolog/monolog": ">=1.8,<1.12",
+ "namshi/jose": "<2.2",
+ "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6",
+ "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13",
+ "nystudio107/craft-seomatic": "<3.3",
+ "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1",
+ "october/backend": ">=1.0.319,<1.0.470",
+ "october/cms": "= 1.0.469|>=1.0.319,<1.0.469",
+ "october/october": ">=1.0.319,<1.0.466",
+ "october/rain": "<1.0.472|>=1.1,<1.1.2",
+ "onelogin/php-saml": "<2.10.4",
+ "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5",
+ "openid/php-openid": "<2.3",
+ "openmage/magento-lts": "<19.4.8|>=20,<20.0.4",
+ "orchid/platform": ">=9,<9.4.4",
+ "oro/crm": ">=1.7,<1.7.4",
+ "oro/platform": ">=1.7,<1.7.4",
+ "padraic/humbug_get_contents": "<1.1.2",
+ "pagarme/pagarme-php": ">=0,<3",
+ "paragonie/random_compat": "<2",
+ "passbolt/passbolt_api": "<2.11",
+ "paypal/merchant-sdk-php": "<3.12",
+ "pear/archive_tar": "<1.4.12",
+ "personnummer/personnummer": "<3.0.2",
+ "phpfastcache/phpfastcache": ">=5,<5.0.13",
+ "phpmailer/phpmailer": "<6.1.6",
+ "phpmussel/phpmussel": ">=1,<1.6",
+ "phpmyadmin/phpmyadmin": "<4.9.6|>=5,<5.0.3",
+ "phpoffice/phpexcel": "<1.8.2",
+ "phpoffice/phpspreadsheet": "<1.16",
+ "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3",
+ "phpwhois/phpwhois": "<=4.2.5",
+ "phpxmlrpc/extras": "<0.6.1",
+ "pimcore/pimcore": "<6.3",
+ "pocketmine/pocketmine-mp": "<3.15.4",
+ "prestashop/autoupgrade": ">=4,<4.10.1",
+ "prestashop/contactform": ">1.0.1,<4.3",
+ "prestashop/gamification": "<2.3.2",
+ "prestashop/productcomments": ">=4,<4.2.1",
+ "prestashop/ps_facetedsearch": "<3.4.1",
+ "privatebin/privatebin": "<1.2.2|>=1.3,<1.3.2",
+ "propel/propel": ">=2-alpha.1,<=2-alpha.7",
+ "propel/propel1": ">=1,<=1.7.1",
+ "pterodactyl/panel": "<0.7.19|>=1-rc.0,<=1-rc.6",
+ "pusher/pusher-php-server": "<2.2.1",
+ "rainlab/debugbar-plugin": "<3.1",
+ "robrichards/xmlseclibs": "<3.0.4",
+ "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1",
+ "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9",
+ "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11",
+ "sensiolabs/connect": "<4.2.3",
+ "serluck/phpwhois": "<=4.2.6",
+ "shopware/core": "<=6.3.4",
+ "shopware/platform": "<=6.3.5",
+ "shopware/shopware": "<5.6.9",
+ "silverstripe/admin": ">=1.0.3,<1.0.4|>=1.1,<1.1.1",
+ "silverstripe/assets": ">=1,<1.4.7|>=1.5,<1.5.2",
+ "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4",
+ "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1",
+ "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3",
+ "silverstripe/framework": "<4.4.7|>=4.5,<4.5.4",
+ "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2|>=3.2,<3.2.4",
+ "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1",
+ "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4",
+ "silverstripe/subsites": ">=2,<2.1.1",
+ "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1",
+ "silverstripe/userforms": "<3",
+ "simple-updates/phpwhois": "<=1",
+ "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4",
+ "simplesamlphp/simplesamlphp": "<1.18.6",
+ "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1",
+ "simplito/elliptic-php": "<1.0.6",
+ "slim/slim": "<2.6",
+ "smarty/smarty": "<3.1.33",
+ "socalnick/scn-social-auth": "<1.15.2",
+ "socialiteproviders/steam": "<1.1",
+ "spoonity/tcpdf": "<6.2.22",
+ "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1",
+ "ssddanbrown/bookstack": "<0.29.2",
+ "stormpath/sdk": ">=0,<9.9.99",
+ "studio-42/elfinder": "<2.1.49",
+ "sulu/sulu": "<1.6.34|>=2,<2.0.10|>=2.1,<2.1.1",
+ "swiftmailer/swiftmailer": ">=4,<5.4.5",
+ "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2",
+ "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1",
+ "sylius/grid-bundle": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1",
+ "sylius/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4",
+ "sylius/sylius": "<1.6.9|>=1.7,<1.7.9|>=1.8,<1.8.3",
+ "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99",
+ "symbiote/silverstripe-versionedfiles": "<=2.0.3",
+ "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8",
+ "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
+ "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4",
+ "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
+ "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
+ "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7",
+ "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.4.13|>=5,<5.1.5",
+ "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
+ "symfony/mime": ">=4.3,<4.3.8",
+ "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
+ "symfony/polyfill": ">=1,<1.10",
+ "symfony/polyfill-php55": ">=1,<1.10",
+ "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
+ "symfony/routing": ">=2,<2.0.19",
+ "symfony/security": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=4.4,<4.4.7|>=5,<5.0.7",
+ "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
+ "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7",
+ "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
+ "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
+ "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7",
+ "symfony/serializer": ">=2,<2.0.11",
+ "symfony/symfony": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.4.13|>=5,<5.1.5",
+ "symfony/translation": ">=2,<2.0.17",
+ "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3",
+ "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8",
+ "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4",
+ "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7",
+ "t3g/svg-sanitizer": "<1.0.3",
+ "tecnickcom/tcpdf": "<6.2.22",
+ "thelia/backoffice-default-template": ">=2.1,<2.1.2",
+ "thelia/thelia": ">=2.1-beta.1,<2.1.3",
+ "theonedemon/phpwhois": "<=4.2.5",
+ "titon/framework": ">=0,<9.9.99",
+ "truckersmp/phpwhois": "<=4.3.1",
+ "twig/twig": "<1.38|>=2,<2.7",
+ "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.38|>=9,<9.5.23|>=10,<10.4.10",
+ "typo3/cms-core": ">=8,<8.7.38|>=9,<9.5.23|>=10,<10.4.10",
+ "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5",
+ "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4",
+ "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1",
+ "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10",
+ "ua-parser/uap-php": "<3.8",
+ "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2",
+ "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4",
+ "wallabag/tcpdf": "<6.2.22",
+ "willdurand/js-translation-bundle": "<2.1.1",
+ "yii2mod/yii2-cms": "<1.9.2",
+ "yiisoft/yii": ">=1.1.14,<1.1.15",
+ "yiisoft/yii2": "<2.0.38",
+ "yiisoft/yii2-bootstrap": "<2.0.4",
+ "yiisoft/yii2-dev": "<2.0.15",
+ "yiisoft/yii2-elasticsearch": "<2.0.5",
+ "yiisoft/yii2-gii": "<2.0.4",
+ "yiisoft/yii2-jui": "<2.0.4",
+ "yiisoft/yii2-redis": "<2.0.8",
+ "yourls/yourls": "<1.7.4",
+ "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3",
+ "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2",
+ "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2",
+ "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5",
+ "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3",
+ "zendframework/zend-diactoros": ">=1,<1.8.4",
+ "zendframework/zend-feed": ">=1,<2.10.3",
+ "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1",
+ "zendframework/zend-http": ">=1,<2.8.1",
+ "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6",
+ "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3",
+ "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2",
+ "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1",
+ "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4",
+ "zendframework/zend-validator": ">=2.3,<2.3.6",
+ "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1",
+ "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6",
+ "zendframework/zendframework": "<2.5.1",
+ "zendframework/zendframework1": "<1.12.20",
+ "zendframework/zendopenid": ">=2,<2.0.2",
+ "zendframework/zendxml": ">=1,<1.0.1",
+ "zetacomponents/mail": "<1.8.2",
+ "zf-commons/zfc-user": "<1.2.2",
+ "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3",
+ "zfr/zfr-oauth2-server-module": "<0.1.2"
+ },
+ "type": "metapackage",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "role": "maintainer"
+ },
+ {
+ "name": "Ilya Tribusean",
+ "email": "slash3b@gmail.com",
+ "role": "maintainer"
+ }
+ ],
+ "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it",
+ "funding": [
+ {
+ "url": "https://github.com/Ocramius",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-02-10T03:02:31+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+ "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T08:15:22+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "1071dfcef776a57013124ff35e1fc41ccd294758"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758",
+ "reference": "1071dfcef776a57013124ff35e1fc41ccd294758",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "sebastian/diff": "^3.0",
+ "sebastian/exporter": "^3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T08:04:30+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
+ "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5 || ^8.0",
+ "symfony/process": "^2 || ^3.3 || ^4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:59:04+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "4.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
+ "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:53:42+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "3.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e",
+ "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:47:53+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2017-04-27T15:39:26+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "3.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+ "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:40:27+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+ "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:37:18+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
+ "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:34:24+00:00"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3",
+ "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:30:19+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "time": "2016-10-03T07:35:21+00:00"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.5.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "9d583721a7157ee997f235f327de038e7ea6dac4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4",
+ "reference": "9d583721a7157ee997f235f327de038e7ea6dac4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "bin": [
+ "bin/phpcs",
+ "bin/phpcbf"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Greg Sherwood",
+ "role": "lead"
+ }
+ ],
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
+ "keywords": [
+ "phpcs",
+ "standards"
+ ],
+ "time": "2020-10-23T02:01:07+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v5.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a",
+ "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php73": "^1.8",
+ "symfony/polyfill-php80": "^1.15",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/string": "^5.1"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<4.4",
+ "symfony/dotenv": "<5.1",
+ "symfony/event-dispatcher": "<4.4",
+ "symfony/lock": "<4.4",
+ "symfony/process": "<4.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/lock": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/lock": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command line",
+ "console",
+ "terminal"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-28T22:06:19+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v5.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "4adc8d172d602008c204c2e16956f99257248e03"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/4adc8d172d602008c204c2e16956f99257248e03",
+ "reference": "4adc8d172d602008c204c2e16956f99257248e03",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-28T22:06:19+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.22.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e",
+ "reference": "c6c942b1ac76c82448322025e084cadc56048b4e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.22-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-07T16:49:33+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.22.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "267a9adeb8ecb8071040a740930e077cdfb987af"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/267a9adeb8ecb8071040a740930e077cdfb987af",
+ "reference": "267a9adeb8ecb8071040a740930e077cdfb987af",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.22-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-07T16:49:33+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.22.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "6e971c891537eb617a00bb07a43d182a6915faba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba",
+ "reference": "6e971c891537eb617a00bb07a43d182a6915faba",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.22-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-07T17:09:11+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.22.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13",
+ "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.22-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-07T16:49:33+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.22.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2",
+ "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.22-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php73\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-07T16:49:33+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.22.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91",
+ "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.22-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-07T16:49:33+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1",
+ "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/container": "^1.0"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-09-07T11:33:47+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v5.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "c95468897f408dd0aca2ff582074423dd0455122"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122",
+ "reference": "c95468897f408dd0aca2ff582074423dd0455122",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php80": "~1.15"
+ },
+ "require-dev": {
+ "symfony/error-handler": "^4.4|^5.0",
+ "symfony/http-client": "^4.4|^5.0",
+ "symfony/translation-contracts": "^1.1|^2",
+ "symfony/var-exporter": "^4.4|^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "files": [
+ "Resources/functions.php"
+ ],
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-01-25T15:14:59+00:00"
+ },
+ {
+ "name": "szepeviktor/phpstan-wordpress",
+ "version": "v0.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/szepeviktor/phpstan-wordpress.git",
+ "reference": "1946738cdec130df4727f780ac541f8c6fd746a2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/1946738cdec130df4727f780ac541f8c6fd746a2",
+ "reference": "1946738cdec130df4727f780ac541f8c6fd746a2",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~7.1",
+ "php-stubs/wordpress-stubs": "^4.7 || ^5.0",
+ "phpstan/phpstan": "^0.12.0",
+ "symfony/polyfill-php73": "^1.12.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.8.6",
+ "consistence/coding-standard": "^3.8",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5",
+ "jakub-onderka/php-parallel-lint": "^1.0",
+ "phpstan/phpstan-strict-rules": "^0.12",
+ "slevomat/coding-standard": "^5.0.4"
+ },
+ "type": "phpstan-extension",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\WordPress\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "WordPress extensions for PHPStan",
+ "keywords": [
+ "PHPStan",
+ "code analyse",
+ "code analysis",
+ "static analysis",
+ "wordpress"
+ ],
+ "time": "2019-12-05T22:19:53+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "75a63c33a8577608444246075ea0af0d052e452a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a",
+ "reference": "75a63c33a8577608444246075ea0af0d052e452a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2020-07-12T23:59:07+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.9.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
+ "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0 || ^8.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<0.12.20",
+ "vimeo/psalm": "<3.9.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.36 || ^7.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "time": "2020-07-08T17:02:28+00:00"
+ },
+ {
+ "name": "woocommerce/woocommerce",
+ "version": "3.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/woocommerce/woocommerce.git",
+ "reference": "310a620134199758e071ad00690816a95ced2100"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/woocommerce/woocommerce/zipball/310a620134199758e071ad00690816a95ced2100",
+ "reference": "310a620134199758e071ad00690816a95ced2100",
+ "shasum": ""
+ },
+ "require": {
+ "automattic/jetpack-autoloader": "^1.2.0",
+ "composer/installers": "1.7.0",
+ "maxmind-db/reader": "1.6.0",
+ "php": ">=5.6|>=7.0",
+ "woocommerce/woocommerce-blocks": "2.5.14",
+ "woocommerce/woocommerce-rest-api": "1.0.7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "7.5.18",
+ "woocommerce/woocommerce-sniffs": "0.0.9"
+ },
+ "type": "wordpress-plugin",
+ "extra": {
+ "installer-paths": {
+ "packages/woocommerce-rest-api": [
+ "woocommerce/woocommerce-rest-api"
+ ],
+ "packages/woocommerce-blocks": [
+ "woocommerce/woocommerce-blocks"
+ ]
+ },
+ "scripts-description": {
+ "test": "Run unit tests",
+ "phpcs": "Analyze code against the WordPress coding standards with PHP_CodeSniffer",
+ "phpcbf": "Fix coding standards warnings/errors automatically with PHP Code Beautifier"
+ }
+ },
+ "autoload": {
+ "exclude-from-classmap": [
+ "includes/legacy",
+ "includes/libraries"
+ ],
+ "psr-4": {
+ "Automattic\\WooCommerce\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-3.0-or-later"
+ ],
+ "description": "An eCommerce toolkit that helps you sell anything. Beautifully.",
+ "homepage": "https://woocommerce.com/",
+ "time": "2020-03-04T09:08:17+00:00"
+ },
+ {
+ "name": "woocommerce/woocommerce-blocks",
+ "version": "v2.5.14",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
+ "reference": "0dd70617085d2e73f3adfb38df98a90df3514816"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/0dd70617085d2e73f3adfb38df98a90df3514816",
+ "reference": "0dd70617085d2e73f3adfb38df98a90df3514816",
+ "shasum": ""
+ },
+ "require": {
+ "automattic/jetpack-autoloader": "1.3.2",
+ "composer/installers": "1.7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "6.5.14",
+ "woocommerce/woocommerce-sniffs": "0.0.7"
+ },
+ "type": "wordpress-plugin",
+ "extra": {
+ "scripts-description": {
+ "phpcs": "Analyze code against the WordPress coding standards with PHP_CodeSniffer",
+ "phpcbf": "Fix coding standards warnings/errors automatically with PHP Code Beautifier"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Automattic\\WooCommerce\\Blocks\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-3.0-or-later"
+ ],
+ "description": "WooCommerce blocks for the Gutenberg editor.",
+ "homepage": "https://woocommerce.com/",
+ "keywords": [
+ "blocks",
+ "gutenberg",
+ "woocommerce"
+ ],
+ "time": "2020-03-03T13:25:56+00:00"
+ },
+ {
+ "name": "woocommerce/woocommerce-rest-api",
+ "version": "1.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/woocommerce/woocommerce-rest-api.git",
+ "reference": "49162ec26a25bd0c6efc0f3452b113cdfff0a823"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/woocommerce/woocommerce-rest-api/zipball/49162ec26a25bd0c6efc0f3452b113cdfff0a823",
+ "reference": "49162ec26a25bd0c6efc0f3452b113cdfff0a823",
+ "shasum": ""
+ },
+ "require": {
+ "automattic/jetpack-autoloader": "^1.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "6.5.14",
+ "woocommerce/woocommerce-sniffs": "0.0.9"
+ },
+ "type": "wordpress-plugin",
+ "autoload": {
+ "classmap": [
+ "src/Controllers/Version1",
+ "src/Controllers/Version2",
+ "src/Controllers/Version3"
+ ],
+ "psr-4": {
+ "Automattic\\WooCommerce\\RestApi\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-3.0-or-later"
+ ],
+ "description": "The WooCommerce core REST API.",
+ "homepage": "https://github.com/woocommerce/woocommerce-rest-api",
+ "abandoned": true,
+ "time": "2020-01-28T21:04:51+00:00"
+ },
+ {
+ "name": "wp-coding-standards/wpcs",
+ "version": "2.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62",
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^3.3.1"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpcsstandards/phpcsdevtools": "^1.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "wordpress"
+ ],
+ "time": "2020-05-13T23:57:56+00:00"
+ },
+ {
+ "name": "wp-media/background-processing",
+ "version": "v1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-media/background-processing.git",
+ "reference": "20979ca3bc258bb97a526c2c4aff61254872bfea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-media/background-processing/zipball/20979ca3bc258bb97a526c2c4aff61254872bfea",
+ "reference": "20979ca3bc258bb97a526c2c4aff61254872bfea",
+ "shasum": ""
+ },
+ "require-dev": {
+ "brain/monkey": "^2.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+ "php": "^5.6 || ^7",
+ "phpcompatibility/phpcompatibility-wp": "^2.0",
+ "phpunit/phpunit": "^5.7 || ^7",
+ "wp-coding-standards/wpcs": "^2",
+ "wp-media/phpunit": "^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ ""
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0+"
+ ],
+ "authors": [
+ {
+ "name": "WP Media",
+ "email": "contact@wp-media.me",
+ "homepage": "https://wp-media.me"
+ }
+ ],
+ "description": "Async & Background Tasks Processing",
+ "homepage": "https://github.com/wp-media/background-processing",
+ "time": "2020-08-21T13:47:06+00:00"
+ },
+ {
+ "name": "wp-media/cloudflare",
+ "version": "v1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-media/module-cloudflare.git",
+ "reference": "2fa5d2c99e7696d71885bda1c14e49e3ff0d5e3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-media/module-cloudflare/zipball/2fa5d2c99e7696d71885bda1c14e49e3ff0d5e3e",
+ "reference": "2fa5d2c99e7696d71885bda1c14e49e3ff0d5e3e",
+ "shasum": ""
+ },
+ "require-dev": {
+ "brain/monkey": "^2.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+ "php": "^5.6 || ^7",
+ "phpcompatibility/phpcompatibility-wp": "^2.0",
+ "phpunit/phpunit": "^5.7 || ^7",
+ "wp-coding-standards/wpcs": "^2",
+ "wp-media/event-manager": "^3.1",
+ "wp-media/options": "^3.0",
+ "wp-media/phpunit": "^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "WPMedia\\Cloudflare\\": "."
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0+"
+ ],
+ "authors": [
+ {
+ "name": "WP Media",
+ "email": "contact@wp-media.me",
+ "homepage": "https://wp-media.me"
+ }
+ ],
+ "description": "Cloudflare Addon",
+ "homepage": "https://github.com/wp-media/cloudflare",
+ "time": "2020-08-24T20:21:34+00:00"
+ },
+ {
+ "name": "wp-media/module-rocketcdn",
+ "version": "v1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-media/module-rocketcdn.git",
+ "reference": "2e73be01d9d035593b1c883e0ea79f85e1bfd1da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-media/module-rocketcdn/zipball/2e73be01d9d035593b1c883e0ea79f85e1bfd1da",
+ "reference": "2e73be01d9d035593b1c883e0ea79f85e1bfd1da",
+ "shasum": ""
+ },
+ "require-dev": {
+ "brain/monkey": "^2.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+ "php": "^5.6 || ^7",
+ "phpcompatibility/phpcompatibility-wp": "^2.0",
+ "phpunit/phpunit": "^5.7 || ^7",
+ "roave/security-advisories": "dev-master",
+ "wp-coding-standards/wpcs": "^2",
+ "wp-media/event-manager": "^3.1",
+ "wp-media/module-container": "^2.4",
+ "wp-media/options": "^3.0",
+ "wp-media/phpunit": "^1.0",
+ "wp-media/phpunit-wp-rocket": "^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "WP_Rocket\\Engine\\CDN\\RocketCDN\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0+"
+ ],
+ "authors": [
+ {
+ "name": "WP Media",
+ "email": "contact@wp-media.me",
+ "homepage": "https://wp-media.me"
+ }
+ ],
+ "description": "Module for RocketCDN integration",
+ "homepage": "https://github.com/wp-media/module-rocketcdn",
+ "time": "2021-01-13T17:03:45+00:00"
+ },
+ {
+ "name": "wp-media/module-varnish",
+ "version": "v1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-media/module-varnish.git",
+ "reference": "698534076da4af54fe4a99f33cd0c947175d564c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-media/module-varnish/zipball/698534076da4af54fe4a99f33cd0c947175d564c",
+ "reference": "698534076da4af54fe4a99f33cd0c947175d564c",
+ "shasum": ""
+ },
+ "require-dev": {
+ "brain/monkey": "^2.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+ "php": "^5.6 || ^7",
+ "phpcompatibility/phpcompatibility-wp": "^2.0",
+ "phpstan/phpstan": "^0.12.3",
+ "phpunit/phpunit": "^5.7 || ^7",
+ "roave/security-advisories": "dev-master",
+ "szepeviktor/phpstan-wordpress": "^0.6",
+ "wp-coding-standards/wpcs": "^2",
+ "wp-media/event-manager": "^3.1",
+ "wp-media/module-container": "^2.4",
+ "wp-media/options": "^3.0",
+ "wp-media/phpunit": "^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "WP_Rocket\\Addon\\Varnish\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0+"
+ ],
+ "authors": [
+ {
+ "name": "WP Media",
+ "email": "contact@wp-media.me",
+ "homepage": "https://wp-media.me"
+ }
+ ],
+ "description": "Varnish Addon for WP Rocket",
+ "homepage": "https://github.com/wp-media/module-varnish",
+ "time": "2020-09-01T14:11:34+00:00"
+ },
+ {
+ "name": "wp-media/phpunit",
+ "version": "v1.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-media/phpunit.git",
+ "reference": "5ea013e3a573c4211512248971145496193d9535"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-media/phpunit/zipball/5ea013e3a573c4211512248971145496193d9535",
+ "reference": "5ea013e3a573c4211512248971145496193d9535",
+ "shasum": ""
+ },
+ "require": {
+ "brain/monkey": "^2.0",
+ "mikey179/vfsstream": "^1.6",
+ "php": "^5.6 || ^7",
+ "phpunit/phpunit": "^5.7 || ^7"
+ },
+ "bin": [
+ "wpmedia-phpunit"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "WPMedia\\PHPUnit\\": "."
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0+"
+ ],
+ "authors": [
+ {
+ "name": "WP Media",
+ "email": "contact@wp-media.me",
+ "homepage": "https://wp-media.me"
+ }
+ ],
+ "description": "PHPUnit extender for bootstrapping unit and WordPress integration test suites.",
+ "homepage": "https://github.com/wp-media/phpunit",
+ "time": "2020-04-08T10:44:10+00:00"
+ },
+ {
+ "name": "wp-media/rocket-lazyload-common",
+ "version": "v2.5.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-media/rocket-lazyload-common.git",
+ "reference": "d1e9eac2b192ab5314ad2f7abed55a62ce732c1a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-media/rocket-lazyload-common/zipball/d1e9eac2b192ab5314ad2f7abed55a62ce732c1a",
+ "reference": "d1e9eac2b192ab5314ad2f7abed55a62ce732c1a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "brain/monkey": "^2.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+ "php": "^5.6 || ^7",
+ "phpcompatibility/phpcompatibility-wp": "^2.0",
+ "phpunit/phpunit": "^5.7 || ^7",
+ "wp-coding-standards/wpcs": "^2.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "RocketLazyload\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "WP Media",
+ "email": "contact@wp-media.me"
+ }
+ ],
+ "description": "Common Code between WP Rocket and Lazyload by WP Rocket",
+ "time": "2021-02-11T21:15:35+00:00"
+ },
+ {
+ "name": "wp-media/wp-imagify-partner",
+ "version": "v1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wp-media/wp-imagify-partner.git",
+ "reference": "c3412007b268a2793432f7d4fed31d2639ee2982"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wp-media/wp-imagify-partner/zipball/c3412007b268a2793432f7d4fed31d2639ee2982",
+ "reference": "c3412007b268a2793432f7d4fed31d2639ee2982",
+ "shasum": ""
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0+"
+ ],
+ "authors": [
+ {
+ "name": "WP Media",
+ "email": "contact@wp-media.me",
+ "homepage": "https://wp-media.me"
+ }
+ ],
+ "description": "A php class allowing WordPress plugin developers to promote Imagify through their own plugin",
+ "homepage": "https://github.com/wp-media/wp-imagify-partner",
+ "keywords": [
+ "Imagify",
+ "wordpress"
+ ],
+ "time": "2020-01-22T22:22:41+00:00"
+ },
+ {
+ "name": "wpackagist-plugin/amp",
+ "version": "1.5.5",
+ "source": {
+ "type": "svn",
+ "url": "https://plugins.svn.wordpress.org/amp/",
+ "reference": "tags/1.5.5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://downloads.wordpress.org/plugin/amp.1.5.5.zip"
+ },
+ "require": {
+ "composer/installers": "~1.0"
+ },
+ "type": "wordpress-plugin",
+ "homepage": "https://wordpress.org/plugins/amp/"
+ },
+ {
+ "name": "wpackagist-plugin/hummingbird-performance",
+ "version": "2.0.1",
+ "source": {
+ "type": "svn",
+ "url": "https://plugins.svn.wordpress.org/hummingbird-performance/",
+ "reference": "tags/2.0.1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://downloads.wordpress.org/plugin/hummingbird-performance.2.0.1.zip"
+ },
+ "require": {
+ "composer/installers": "~1.0"
+ },
+ "type": "wordpress-plugin",
+ "homepage": "https://wordpress.org/plugins/hummingbird-performance/"
+ },
+ {
+ "name": "wpackagist-plugin/pdf-embedder",
+ "version": "4.6.1",
+ "source": {
+ "type": "svn",
+ "url": "https://plugins.svn.wordpress.org/pdf-embedder/",
+ "reference": "tags/4.6.1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://downloads.wordpress.org/plugin/pdf-embedder.4.6.1.zip"
+ },
+ "require": {
+ "composer/installers": "~1.0"
+ },
+ "type": "wordpress-plugin",
+ "homepage": "https://wordpress.org/plugins/pdf-embedder/"
+ },
+ {
+ "name": "wpackagist-plugin/simple-custom-css",
+ "version": "4.0.4",
+ "source": {
+ "type": "svn",
+ "url": "https://plugins.svn.wordpress.org/simple-custom-css/",
+ "reference": "trunk"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://downloads.wordpress.org/plugin/simple-custom-css.zip?timestamp=1591707703"
+ },
+ "require": {
+ "composer/installers": "~1.0"
+ },
+ "type": "wordpress-plugin",
+ "homepage": "https://wordpress.org/plugins/simple-custom-css/"
+ },
+ {
+ "name": "wpackagist-plugin/spinupwp",
+ "version": "1.3",
+ "source": {
+ "type": "svn",
+ "url": "https://plugins.svn.wordpress.org/spinupwp/",
+ "reference": "tags/1.3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://downloads.wordpress.org/plugin/spinupwp.1.3.zip"
+ },
+ "require": {
+ "composer/installers": "~1.0"
+ },
+ "type": "wordpress-plugin",
+ "homepage": "https://wordpress.org/plugins/spinupwp/"
+ },
+ {
+ "name": "wpackagist-plugin/wp-smushit",
+ "version": "3.8.2",
+ "source": {
+ "type": "svn",
+ "url": "https://plugins.svn.wordpress.org/wp-smushit/",
+ "reference": "tags/3.8.2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://downloads.wordpress.org/plugin/wp-smushit.3.8.2.zip"
+ },
+ "require": {
+ "composer/installers": "~1.0"
+ },
+ "type": "wordpress-plugin",
+ "homepage": "https://wordpress.org/plugins/wp-smushit/"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {
+ "roave/security-advisories": 20
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=7.0"
+ },
+ "platform-dev": {
+ "php": "^7"
+ },
+ "plugin-api-version": "1.1.0"
+}
diff --git a/wp-content/plugins/wp-rocket/contributors.txt b/wp-content/plugins/wp-rocket/contributors.txt
new file mode 100644
index 00000000..66e7deae
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/contributors.txt
@@ -0,0 +1,22 @@
+This file contains a list of people who have made large contributions to WP Rocket.
+
+Developers:
+ Jonathan Buttigieg
+ Remy Perona
+ Arun Basil Lal
+ Cristina Soponar
+ Tonya Mork
+ Ahmed Saeed
+ Caspar Green
+ Vasilis Manthos
+ Alfonso Catron
+
+QA:
+ Piotr BÄ
k
+
+Previous contributors:
+ Julio Potier
+ Gregory Viguier
+ David Acuna
+ Caspar Hübinger
+ Thomas Geisen
\ No newline at end of file
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/3rd-party.php b/wp-content/plugins/wp-rocket/inc/3rd-party/3rd-party.php
new file mode 100644
index 00000000..fd8b99c2
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/3rd-party.php
@@ -0,0 +1,87 @@
+pagination_base );
+
+ rocket_godaddy_request( 'PURGE', $home_url );
+ rocket_godaddy_request( 'PURGE', $home_pagination_url );
+}
+add_action( 'before_rocket_clean_home', 'rocket_clean_home_godaddy', 10, 2 );
+
+/**
+ * Perform the call to the Varnish server to purge
+ *
+ * @since 2.9.5
+ * @source WPaaS\Cache
+ *
+ * @param string $method can be BAN or PURGE.
+ * @param string $url URL to purge.
+ * @return void
+ */
+function rocket_godaddy_request( $method, $url = null ) {
+ if ( ! method_exists( 'WPass\Plugin', 'vip' ) ) {
+ return;
+ }
+
+ if ( empty( $url ) ) {
+ $url = home_url();
+ }
+
+ $host = rocket_extract_url_component( $url, PHP_URL_HOST );
+ $url = set_url_scheme( str_replace( $host, WPaas\Plugin::vip(), $url ), 'http' );
+
+ wp_cache_flush();
+
+ // This forces the APC cache to flush across the server.
+ update_option( 'gd_system_last_cache_flush', time() );
+
+ wp_remote_request(
+ esc_url_raw( $url ),
+ [
+ 'method' => $method,
+ 'blocking' => false,
+ 'headers' => [
+ 'Host' => $host,
+ ],
+ ]
+ );
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/kinsta.php b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/kinsta.php
new file mode 100644
index 00000000..a131745a
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/kinsta.php
@@ -0,0 +1,166 @@
+kinsta_cache_purge ) ) {
+ $kinsta_cache->kinsta_cache_purge->purge_complete_caches();
+ }
+ }
+ add_action( 'after_rocket_clean_domain', 'rocket_clean_kinsta_cache' );
+
+ /**
+ * Partially clear Kinsta cache when partially clearing WP Rocket cache
+ *
+ * @since 3.0
+ * @author Remy Perona
+ *
+ * @param object $post Post object.
+ * @return void
+ */
+ function rocket_clean_kinsta_post_cache( $post ) {
+ global $kinsta_cache;
+ $kinsta_cache->kinsta_cache_purge->initiate_purge( $post->ID, 'post' );
+ }
+ add_action( 'after_rocket_clean_post', 'rocket_clean_kinsta_post_cache' );
+
+ /**
+ * Clears Kinsta cache for the homepage URL when using "Purge this URL" from the admin bar on the front end
+ *
+ * @since 3.0.4
+ * @author Remy Perona
+ *
+ * @param string $root WP Rocket root cache path.
+ * @param string $lang Current language.
+ * @return void
+ */
+ function rocket_clean_kinsta_cache_home( $root = '', $lang = '' ) {
+ $url = get_rocket_i18n_home_url( $lang );
+ $url = trailingslashit( $url ) . 'kinsta-clear-cache/';
+
+ wp_remote_get(
+ $url,
+ [
+ 'blocking' => false,
+ 'timeout' => 0.01,
+ ]
+ );
+ }
+ add_action( 'after_rocket_clean_home', 'rocket_clean_kinsta_cache_home', 10, 2 );
+
+ /**
+ * Clears Kinsta cache for a specific URL when using "Purge this URL" from the admin bar on the front end
+ *
+ * @since 3.0.4
+ * @author Remy Perona
+ *
+ * @param string $url URL to purge.
+ * @return void
+ */
+ function rocket_clean_kinsta_cache_url( $url ) {
+ $url = trailingslashit( $url ) . 'kinsta-clear-cache/';
+
+ wp_remote_get(
+ $url,
+ [
+ 'blocking' => false,
+ 'timeout' => 0.01,
+ ]
+ );
+ }
+ add_action( 'after_rocket_clean_file', 'rocket_clean_kinsta_cache_url' );
+
+ /**
+ * Remove WP Rocket functions on WP core action hooks to prevent triggering a double cache clear.
+ *
+ * @since 3.0
+ * @author Remy Perona
+ *
+ * @return void
+ */
+ function rocket_remove_partial_purge_hooks() {
+ // WP core action hooks rocket_clean_post() gets hooked into.
+ $clean_post_hooks = [
+ // Disables the refreshing of partial cache when content is edited.
+ 'wp_trash_post',
+ 'delete_post',
+ 'clean_post_cache',
+ 'wp_update_comment_count',
+ ];
+
+ // Remove rocket_clean_post() from core action hooks.
+ array_map(
+ function( $hook ) {
+ remove_action( $hook, 'rocket_clean_post' );
+ },
+ $clean_post_hooks
+ );
+
+ remove_filter( 'rocket_clean_files', 'rocket_clean_files_users' );
+ }
+ add_action( 'wp_rocket_loaded', 'rocket_remove_partial_purge_hooks' );
+
+ if ( \Kinsta\CDN_Enabler::cdn_is_enabled() ) {
+ /**
+ * Add Kinsta CDN to WP Rocket CDN hosts list if enabled
+ *
+ * @since 3.0
+ * @author Remy Perona
+ *
+ * @param Array $hosts Array of CDN hosts.
+ * @return Array Updated array of CDN hosts
+ */
+ function rocket_add_kinsta_cdn_cname( $hosts ) {
+ if ( ! isset( $_SERVER['KINSTA_CDN_DOMAIN'] ) ) {
+ return $hosts;
+ }
+
+ $hosts[] = sanitize_text_field( wp_unslash( $_SERVER['KINSTA_CDN_DOMAIN'] ) );
+
+ return $hosts;
+ }
+ add_filter( 'rocket_cdn_cnames', 'rocket_add_kinsta_cdn_cname', 1 );
+ }
+} else {
+ add_action(
+ 'admin_notices',
+ function() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+
+ $screen = get_current_screen();
+
+ if ( 'settings_page_wprocket' !== $screen->id ) {
+ return;
+ }
+
+ rocket_notice_html(
+ [
+ 'status' => 'error',
+ 'dismissible' => '',
+ // translators: %1$s = opening link tag, %2$s = closing link tag.
+ 'message' => sprintf( __( 'Your installation seems to be missing core Kinsta files managing Cache clearing and CDN, which will prevent your Kinsta installation and WP Rocket from working correctly. Please get in touch with Kinsta support through your %1$sMyKinsta%2$s account to resolve this issue.', 'rocket' ), '', ' ' ),
+ ]
+ );
+ }
+ );
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/nginx.php b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/nginx.php
new file mode 100644
index 00000000..e9107760
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/nginx.php
@@ -0,0 +1,24 @@
+purgeAll();
+ }
+}
+add_action( 'after_rocket_clean_domain', 'rocket_clean_pagely' );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/pressidium.php b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/pressidium.php
new file mode 100644
index 00000000..f2342e93
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/pressidium.php
@@ -0,0 +1,61 @@
+purgeAllCaches();
+ }
+ add_action( 'after_rocket_clean_domain', 'rocket_clean_pressidium' );
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/presslabs.php b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/presslabs.php
new file mode 100644
index 00000000..1ac7bb1c
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/presslabs.php
@@ -0,0 +1,98 @@
+invalidate_url( $permalink[0], true );
+ $cache_handler->invalidate_url( home_url( '/' ), true );
+ $cache_handler->purge_cache( 'listing' );
+}
+
+/**
+ * We clear the cache for the homepage URL when using "Purge this URL" from the admin bar on the front end.
+ *
+ * @since 3.3
+ *
+ * @param string $root WP Rocket root cache path.
+ * @param string $lang Current language.
+ *
+ * @return void
+ */
+function rocket_pl_clean_home( $root = false, $lang = false ) {
+ if ( ! $post || ! $permalink ) {
+ return;
+ }
+
+ $cache_handler = new \Presslabs\Cache\CacheHandler();
+ $cache_handler->invalidate_url( home_url( '/' ), true );
+}
+
+/**
+ * Remove WP Rocket functions on WP core action hooks to prevent triggering a double cache clear.
+ *
+ * @since 3.3
+ *
+ * @return void
+ */
+function rocket_remove_partial_purge_hooks() {
+ // WP core action hooks rocket_clean_post() gets hooked into.
+ $clean_post_hooks = [
+ // Disables the refreshing of partial cache when content is edited.
+ 'wp_trash_post',
+ 'delete_post',
+ 'clean_post_cache',
+ 'wp_update_comment_count',
+ ];
+ // Remove rocket_clean_post() from core action hooks.
+ array_map(
+ function( $hook ) {
+ remove_action( $hook, 'rocket_clean_post' );
+ },
+ $clean_post_hooks
+ );
+ remove_filter( 'rocket_clean_files', 'rocket_clean_files_users' );
+}
+
+if ( ! defined( 'DISABLE_CDN_OFFLOAD' ) && defined( 'PL_CDN_HOST' ) ) {
+ /**
+ * If we have CDN enabled we'll add our HOST to the list.
+ *
+ * @since 3.3
+ *
+ * @param array $hosts Array of CDN hosts.
+ *
+ * @return array Updated array of CDN hosts
+ */
+ function rocket_add_pl_cdn( $hosts ) {
+ $hosts[] = constant( 'PL_CDN_HOST' );
+ return $hosts;
+ }
+ add_filter( 'rocket_cdn_cnames', 'rocket_add_pl_cdn', 1 );
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/siteground.php b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/siteground.php
new file mode 100644
index 00000000..b8150b27
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/siteground.php
@@ -0,0 +1,87 @@
+ 'Version' ] );
+ $version = $sg_optimizer['Version'];
+
+ return $version;
+}
+
+/**
+ * Checks if SG Optimizer Supercache is active.
+ *
+ * @since 3.2.3.1
+ * @author Remy Perona
+ *
+ * @return bool
+ */
+function rocket_is_supercacher_active() {
+ if ( ! version_compare( rocket_get_sg_optimizer_version(), '5.0' ) < 0 ) {
+ global $sg_cachepress_environment;
+
+ return isset( $sg_cachepress_environment ) && $sg_cachepress_environment instanceof SG_CachePress_Environment && $sg_cachepress_environment->cache_is_enabled();
+ }
+
+ return (bool) get_option( 'siteground_optimizer_enable_cache', 0 );
+}
+
+/**
+ * Call the cache server to purge the cache with SuperCacher (SiteGround).
+ *
+ * @since 2.3
+ *
+ * @return void
+ */
+function rocket_clean_supercacher() {
+ if ( ! rocket_is_supercacher_active() ) {
+ return;
+ }
+
+ if ( ! version_compare( rocket_get_sg_optimizer_version(), '5.0' ) < 0 ) {
+ SiteGround_Optimizer\Supercacher\Supercacher::purge_cache();
+ } elseif ( isset( $sg_cachepress_supercacher ) && $sg_cachepress_supercacher instanceof SG_CachePress_Supercacher ) {
+ $sg_cachepress_supercacher->purge_cache();
+ }
+}
+
+if ( rocket_is_supercacher_active() ) {
+ add_action( 'admin_post_sg-cachepress-purge', 'rocket_clean_domain', 0 );
+ add_action( 'after_rocket_clean_domain', 'rocket_clean_supercacher' );
+ add_filter( 'rocket_display_varnish_options_tab', '__return_false' );
+ // Prevent mandatory cookies on hosting with server cache.
+ add_filter( 'rocket_cache_mandatory_cookies', '__return_empty_array', PHP_INT_MAX );
+
+ /**
+ * Force WP Rocket caching on SG Optimizer versions before 4.0.5.
+ *
+ * @since 3.0.4
+ * @author Arun Basil Lal
+ *
+ * @link https://github.com/wp-media/wp-rocket/issues/925
+ */
+ if ( version_compare( rocket_get_sg_optimizer_version(), '4.0.5' ) < 0 ) {
+ add_filter( 'do_rocket_generate_caching_files', '__return_true', 11 );
+ }
+
+ if ( version_compare( rocket_get_sg_optimizer_version(), '5.0' ) < 0 ) {
+ add_action( 'wp_ajax_sg-cachepress-purge', 'rocket_clean_domain', 0 );
+ } else {
+ add_action( 'wp_ajax_admin_bar_purge_cache', 'rocket_clean_domain', 0 );
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/wp-serveur.php b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/wp-serveur.php
new file mode 100644
index 00000000..b1293801
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/hosting/wp-serveur.php
@@ -0,0 +1,35 @@
+
+ */
+if ( class_exists( 'GeotWP\GeotargetingWP' ) ) :
+
+ add_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 72 );
+ add_filter( 'rocket_cache_dynamic_cookies', 'rocket_add_geotargetingwp_dynamic_cookies' );
+ add_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_geotargetingwp_mandatory_cookie' );
+
+ /**
+ * If we recently deactivated a plugin of the family but
+ * we still see the class it means another plugin is still active,
+ * so flush rules once more to be safe
+ */
+ if ( get_option( 'geotWP-deactivated' ) ) {
+ // Update the WP Rocket rules on the .htaccess file.
+ add_action( 'admin_init', 'flush_rocket_htaccess' );
+
+ // Regenerate the config file.
+ add_action( 'admin_init', 'rocket_generate_config_file' );
+ delete_option( 'geotWP-deactivated' );
+ }
+endif;
+
+/**
+ * Add cookies when we activate any goetargetingWP plugin.
+ *
+ * @since 2.10.3
+ * @author Damian Logghe
+ */
+function rocket_activate_geotargetingwp() {
+ add_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 72 );
+ add_filter( 'rocket_cache_dynamic_cookies', 'rocket_add_geotargetingwp_dynamic_cookies' );
+ add_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_geotargetingwp_mandatory_cookie' );
+
+ // Update the WP Rocket rules on the .htaccess file.
+ flush_rocket_htaccess();
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+}
+add_action( 'geotWP/activated', 'rocket_activate_geotargetingwp', 11 );
+
+/**
+ * Remove cookies when we deactivate the plugin.
+ *
+ * @since 2.10.3
+ * @author Damian Logghe
+ */
+function rocket_deactivate_geotargetingwp() {
+ // add into db a record saying we deactivated one of the family plugins.
+ update_option( 'geotWP-deactivated', true );
+ remove_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 72 );
+ remove_filter( 'rocket_cache_dynamic_cookies', 'rocket_add_geotargetingwp_dynamic_cookies' );
+ remove_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_geotargetingwp_mandatory_cookie' );
+
+ // Update the WP Rocket rules on the .htaccess file.
+ flush_rocket_htaccess();
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+}
+add_action( 'geotWP/deactivated', 'rocket_deactivate_geotargetingwp', 11 );
+
+/**
+ * Add the GeotargetingWP cookies to generate caching files depending on their values.
+ *
+ * @since 2.10.3
+ * @author Damian Logghe
+ *
+ * @param Array $cookies An array of cookies.
+ * @return Array Updated array of cookies
+ */
+function rocket_add_geotargetingwp_dynamic_cookies( $cookies ) {
+ return rocket_add_geot_cookies( $cookies );
+}
+
+/**
+ * Add the GeotargetingWP cookies to the list of mandatory cookies before to generate caching files.
+ *
+ * @since 2.10.3
+ * @author Damian Logghe
+ *
+ * @param Array $cookies An array of cookies.
+ * @return Array Updated array of cookies
+ */
+function rocket_add_geotargetingwp_mandatory_cookie( $cookies ) {
+ return rocket_add_geot_cookies( $cookies );
+}
+
+/**
+ * Let users modify cache level by default set to country.
+ *
+ * @since 2.10.3
+ * @author Damian Logghe
+ *
+ * @param Array $cookies An array of cookies.
+ * @return Array Updated array of cookies
+ */
+function rocket_add_geot_cookies( $cookies ) {
+ // valid options are country, state, city.
+ $enabled_cookies = apply_filters( 'rocket_geotargetingwp_enabled_cookies', [ 'country' ] );
+ foreach ( $enabled_cookies as $enabled_cookie ) {
+ if ( ! in_array( 'geot_rocket_' . $enabled_cookie, $cookies, true ) ) {
+ $cookies[] = 'geot_rocket_' . $enabled_cookie;
+ }
+ }
+ return $cookies;
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/i18n/polylang.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/i18n/polylang.php
new file mode 100644
index 00000000..0df3902e
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/i18n/polylang.php
@@ -0,0 +1,145 @@
+options['force_lang'] ) {
+ rocket_clean_cache_dir();
+ }
+ }
+ add_action( 'after_rocket_clean_domain', 'rocket_force_clean_domain_on_polylang' );
+
+ // Filter mandatory cookies and WP Rocket rewrite rules if Polylang module 'Detect browser language' is enabled.
+ if ( function_exists( 'PLL' ) && PLL()->options['browser'] ) {
+
+ // Add Polylang's language cookie as a mandatory cookie.
+ add_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_polylang_mandatory_cookie' );
+
+ // Remove WP Rocket rewrite rules from .htaccess file.
+ add_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 74 );
+ }
+endif;
+
+/**
+ * Add Polylang's language cookie to the mandatory cookies of WP Rocket.
+ *
+ * Polylang saves the users preferred language in this cookie by detecting browser language or by user choice
+ * Adding this as a mandatory cookie prevents WP Rocket from serving the cache when the cookie is not set.
+ *
+ * @param array $cookies Array with mandatory cookies.
+ * @return (array) Array of mandatory cookies with the Polylang cookie appended
+ *
+ * @author Arun Basil Lal
+ * @since 3.0.5
+ */
+function rocket_add_polylang_mandatory_cookie( $cookies ) {
+ $cookies[] = defined( 'PLL_COOKIE' ) ? PLL_COOKIE : 'pll_language';
+
+ return $cookies;
+}
+
+/**
+ * Add mandatory cookie to WP Rocket config and remove rewrite rules from .htaccess on Polylang activation.
+ *
+ * Add mandatory cookie only if the Polylang module 'Detect browser language' is active.
+ * Also purge the homepage cache.
+ *
+ * @author Arun Basil Lal
+ * @since 3.0.5
+ */
+function rocket_activate_polylang() {
+ // Read Polylang settings from db.
+ $polylang_settings = get_option( 'polylang' );
+
+ if ( isset( $polylang_settings['browser'] ) && ( 1 === (int) $polylang_settings['browser'] ) ) {
+ // Add Polylang's language cookie as a mandatory cookie.
+ add_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_polylang_mandatory_cookie' );
+
+ // Remove WP Rocket rewrite rules from .htaccess file.
+ add_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 74 );
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+
+ // Regenerate .htaccess file.
+ flush_rocket_htaccess();
+
+ // Purge homepage cache.
+ rocket_clean_home();
+ }
+}
+add_action( 'activate_polylang/polylang.php', 'rocket_activate_polylang', 11 );
+
+/**
+ * Remove mandatory cookie and add rewrite rules back to .htaccess when Polylang is deactivated.
+ *
+ * @author Arun Basil Lal
+ * @since 3.0.5
+ */
+function rocket_deactivate_polylang() {
+ // Remove Polylang's language cookie as a mandatory cookie.
+ remove_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_polylang_mandatory_cookie' );
+
+ // Add back WP Rocket rewrite rules from .htaccess file.
+ remove_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 74 );
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+
+ // Regenerate .htaccess file.
+ flush_rocket_htaccess();
+}
+add_action( 'deactivate_polylang/polylang.php', 'rocket_deactivate_polylang', 11 );
+
+/**
+ * Update mandatory cookie in WP Rocket config file and remove rewrite rules from .htaccess
+ * when Detect browser language module is enabled / disabled.
+ *
+ * @param array $value Array containing Polylang settings before its written to db.
+ * @return array
+ *
+ * @author Arun Basil Lal
+ * @since 3.0.5
+ */
+function rocket_detect_browser_language_status_change( $value ) {
+ if ( function_exists( 'PLL' ) && PLL()->options['browser'] ) {
+
+ // Add Polylang's language cookie as a mandatory cookie.
+ add_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_polylang_mandatory_cookie' );
+
+ // Remove WP Rocket rewrite rules from .htaccess file.
+ add_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 74 );
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+
+ // Regenerate .htaccess file.
+ flush_rocket_htaccess();
+
+ // Purge homepage cache.
+ rocket_clean_home();
+ } else {
+ // Remove Polylang's language cookie as a mandatory cookie.
+ remove_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_polylang_mandatory_cookie' );
+
+ // Add back WP Rocket rewrite rules from .htaccess file.
+ remove_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 74 );
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+
+ // Regenerate .htaccess file.
+ flush_rocket_htaccess();
+ }
+
+ return $value;
+}
+add_filter( 'pre_update_option_polylang', 'rocket_detect_browser_language_status_change' );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/i18n/wpml.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/i18n/wpml.php
new file mode 100644
index 00000000..6f3482bd
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/i18n/wpml.php
@@ -0,0 +1,11 @@
+ 'sitemap_preload',
+ 'type' => 'checkbox',
+ 'label' => __( 'Jetpack XML Sitemaps', 'rocket' ),
+ 'label_for' => 'jetpack_xml_sitemap',
+ 'label_screen' => sprintf( __( 'Preload the sitemap from the Jetpack plugin', 'rocket' ), 'Jetpack' ),
+ 'default' => 0,
+ ];
+ $options[] = [
+ 'parent' => 'sitemap_preload',
+ 'type' => 'helper_description',
+ 'name' => 'jetpack_xml_sitemap_desc',
+ // translators: %s = plugin name, e.g. Yoast SEO.
+ 'description' => sprintf( __( 'We automatically detected the sitemap generated by the %s plugin. You can check the option to preload it.', 'rocket' ), 'Jetpack' ),
+ ];
+
+ return $options;
+ }
+ add_filter( 'rocket_sitemap_preload_options', 'rocket_sitemap_preload_jetpack_option' );
+ } // End if().
+
+ /**
+ * Support Jetpack's EU Cookie Law Widget.
+ *
+ * @see https://jetpack.com/support/extra-sidebar-widgets/eu-cookie-law-widget/
+ *
+ * @since 2.10.1
+ * @author Jeremy Herve
+ */
+ if ( Jetpack::is_module_active( 'widgets' ) ) :
+
+ /**
+ * Add the EU Cookie Law to the list of mandatory cookies before generating caching files.
+ *
+ * @since 2.10.1
+ * @author Jeremy Herve
+ *
+ * @param array $cookies List of mandatory cookies.
+ */
+ function rocket_add_jetpack_cookie_law_mandatory_cookie( $cookies ) {
+ $cookies['jetpack-eu-cookie-law'] = 'eucookielaw';
+
+ return $cookies;
+ }
+ add_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_jetpack_cookie_law_mandatory_cookie' );
+
+ // Don't add the WP Rocket rewrite rules to avoid issues.
+ add_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 76 );
+
+ /**
+ * Add Jetpack cookie when:
+ * - Jetpack is active.
+ * - Jetpack's Extra Sidebar Widgets module is active.
+ * - The widget is active.
+ * - the rocket_jetpack_eu_cookie_widget option is empty or not set.
+ *
+ * @since 2.10.1
+ * @author Jeremy Herve
+ */
+ function rocket_activate_jetpack_cookie_law() {
+ $rocket_jp_eu_cookie_widget = get_option( 'rocket_jetpack_eu_cookie_widget' );
+
+ if (
+ is_active_widget( false, false, 'eu_cookie_law_widget' )
+ && empty( $rocket_jp_eu_cookie_widget )
+ ) {
+ add_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 76 );
+ add_filter( 'rocket_cache_mandatory_cookies', 'rocket_add_jetpack_cookie_law_mandatory_cookie' );
+
+ // Update the WP Rocket rules on the .htaccess file.
+ flush_rocket_htaccess();
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+
+ // Set the option, so this is not triggered again.
+ update_option( 'rocket_jetpack_eu_cookie_widget', 1, true );
+ }
+ }
+ add_action( 'admin_init', 'rocket_activate_jetpack_cookie_law' );
+
+ endif; // End if Widgets module is active check.
+
+endif; // End if Jetpack is active check.
+
+/**
+ * Remove cookies if Jetpack gets deactivated.
+ *
+ * @since 2.10.1
+ * @author Jeremy Herve
+ */
+function rocket_remove_jetpack_cookie_law_mandatory_cookie() {
+ remove_filter( 'rocket_htaccess_mod_rewrite', '__return_false', 76 );
+ remove_filter( 'rocket_cache_mandatory_cookies', '_rocket_add_eu_cookie_law_mandatory_cookie' );
+
+ // Update the WP Rocket rules on the .htaccess file.
+ flush_rocket_htaccess();
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+
+ // Delete our option.
+ delete_option( 'rocket_jetpack_eu_cookie_widget' );
+}
+add_action( 'deactivate_jetpack/jetpack.php', 'rocket_remove_jetpack_cookie_law_mandatory_cookie', 11 );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/mailchimp.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/mailchimp.php
new file mode 100644
index 00000000..cb06046d
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/mailchimp.php
@@ -0,0 +1,36 @@
+is_dir( $cache_path ) ) {
+ rocket_mkdir_p( $cache_path );
+ }
+
+ if ( ! rocket_direct_filesystem()->exists( $css_path ) ) {
+ ob_start();
+ mailchimpSF_main_css();
+ $content = ob_get_contents();
+ ob_end_clean();
+
+ rocket_put_content( $css_path, $content );
+ }
+
+ wp_deregister_style( 'mailchimpSF_main_css' );
+ wp_register_style( 'mailchimpSF_main_css', $cache_url . 'mailchimpSF_main_css.css', null, MCSF_VER );
+}
+add_action( 'init', 'rocket_fix_mailchimp_main_css', PHP_INT_MAX );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/mobile/wp-appkit.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/mobile/wp-appkit.php
new file mode 100644
index 00000000..68e669f4
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/mobile/wp-appkit.php
@@ -0,0 +1,56 @@
+purge_url( $url );
+ }
+ add_action( 'after_rocket_clean_home', 'rocket_clean_nginx_cache_home', 10, 2 );
+
+ /**
+ * Clears NGINX cache for a specific URL when using "Purge this URL" from the admin bar on the front end
+ *
+ * @since 3.3.0.1
+ * @author Remy Perona
+ *
+ * @param string $url URL to purge.
+ * @return void
+ */
+ function rocket_clean_nginx_cache_url( $url ) {
+ global $nginx_purger;
+
+ if ( ! isset( $nginx_purger ) ) {
+ return;
+ }
+
+ if ( ! isset( $_GET['type'], $_GET['_wpnonce'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ return;
+ }
+
+ if ( false !== strpos( $url, 'index.html' ) ) {
+ return;
+ }
+
+ if ( 'page' === substr( $url, -4 ) ) {
+ return;
+ }
+
+ $url = str_replace( '*', '', $url );
+
+ $nginx_purger->purge_url( $url );
+ }
+ add_action( 'after_rocket_clean_file', 'rocket_clean_nginx_cache_url' );
+
+ /**
+ * Clean the NGINX cache using Nginx Helper after WP Rocket's cache is purged.
+ *
+ * @since 3.3.0.1
+ */
+ function rocket_clean_nginx_helper_cache() {
+ if ( isset( $_GET['nginx_helper_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ return;
+ }
+
+ do_action( 'rt_nginx_helper_purge_all' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ }
+ add_action( 'after_rocket_clean_domain', 'rocket_clean_nginx_helper_cache' );
+endif;
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/page-builder/thrive-visual-editor.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/page-builder/thrive-visual-editor.php
new file mode 100644
index 00000000..a940aea8
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/page-builder/thrive-visual-editor.php
@@ -0,0 +1,16 @@
+ $slug ) {
+ $sfml_slugs[ $key ] = $slug . '?';
+ }
+
+ return array_merge( $urls, $sfml_slugs );
+}
+
+/**
+ * Add SFML custom urls to caching exclusion when activating the plugin
+ *
+ * @since 2.9.3
+ */
+function rocket_activate_sfml() {
+ if ( defined( 'SFML_VERSION' ) ) {
+ add_filter( 'rocket_cache_reject_uri', 'rocket_add_sfml_exclude_pages' );
+
+ // Update the WP Rocket rules on the .htaccess.
+ flush_rocket_htaccess();
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+ }
+}
+add_action( 'activate_sf-move-login/sf-move-login.php', 'rocket_activate_sfml', 11 );
+
+/**
+ * Remove SFML custom urls from caching exclusion when deactivating the plugin
+ *
+ * @since 2.9.3
+ */
+function rocket_deactivate_sfml() {
+ if ( defined( 'SFML_VERSION' ) ) {
+ remove_filter( 'rocket_cache_reject_uri', 'rocket_add_sfml_exclude_pages' );
+
+ // Update the WP Rocket rules on the .htaccess.
+ flush_rocket_htaccess();
+
+ // Regenerate the config file.
+ rocket_generate_config_file();
+ }
+}
+add_action( 'deactivate_sf-move-login/sf-move-login.php', 'rocket_deactivate_sfml', 11 );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/security/wps-hide-login.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/security/wps-hide-login.php
new file mode 100644
index 00000000..94b08e7e
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/security/wps-hide-login.php
@@ -0,0 +1,62 @@
+new_login_url() );
+ } elseif ( class_exists( '\WPS\WPS_Hide_Login\Plugin' ) ) {
+ $urls[] = rocket_clean_exclude_file( \WPS\WPS_Hide_Login\Plugin::get_instance()->new_login_url() );
+ }
+
+ return $urls;
+}
+
+/**
+ * Add WPS Hide Login custom url to caching exclusion when activating the plugin
+ *
+ * @since 2.11
+ */
+function rocket_activate_wps_hide_login() {
+ add_filter( 'rocket_cache_reject_uri', 'rocket_exlude_wps_hide_login_page' );
+
+ // Update .htaccess file rules.
+ flush_rocket_htaccess();
+
+ // Update config file.
+ rocket_generate_config_file();
+}
+add_action( 'activate_wps-hide-login/wps-hide-login.php', 'rocket_activate_wps_hide_login', 11 );
+
+/**
+ * Remove WPS Hide Login custom url from caching exclusion when deactivating the plugin
+ *
+ * @since 2.11
+ */
+function rocket_deactivate_wps_hide_login() {
+ remove_filter( 'rocket_cache_reject_uri', 'rocket_exlude_wps_hide_login_page' );
+
+ // Update .htaccess file rules.
+ flush_rocket_htaccess();
+
+ // Update config file.
+ rocket_generate_config_file();
+}
+add_action( 'deactivate_wps-hide-login/wps-hide-login.php', 'rocket_deactivate_wps_hide_login', 11 );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/all-in-one-seo-pack.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/all-in-one-seo-pack.php
new file mode 100644
index 00000000..df624b66
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/all-in-one-seo-pack.php
@@ -0,0 +1,130 @@
+options->sitemap->general->enable )
+ ) {
+ /**
+ * Add All in One SEO Sitemap option to WP Rocket options
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @param Array $options Array of WP Rocket options.
+ * @return Array Updated array of WP Rocket options
+ */
+ function rocket_add_all_in_one_seo_sitemap_option( $options ) {
+ $options['all_in_one_seo_xml_sitemap'] = 0;
+
+ return $options;
+ }
+ add_filter( 'rocket_first_install_options', 'rocket_add_all_in_one_seo_sitemap_option' );
+
+ /**
+ * Sanitize the AIO SEO option value
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @param Array $inputs Array of inputs values.
+ * @return Array Updated array of inputs $values
+ */
+ function rocket_all_in_one_seo_sitemap_option_sanitize( $inputs ) {
+ $inputs['all_in_one_seo_xml_sitemap'] = ! empty( $inputs['all_in_one_seo_xml_sitemap'] ) ? 1 : 0;
+
+ return $inputs;
+ }
+ add_filter( 'rocket_inputs_sanitize', 'rocket_all_in_one_seo_sitemap_option_sanitize' );
+
+ /**
+ * Add All in One SEO Sitemap to the preload list
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @param Array $sitemaps Array of sitemaps to preload.
+ * @return Array Updated array of sitemaps to preload
+ */
+ function rocket_add_all_in_one_seo_sitemap( $sitemaps ) {
+ if ( ! get_rocket_option( 'all_in_one_seo_xml_sitemap', false ) ) {
+ return $sitemaps;
+ }
+
+ $aioseo_v3 = defined( 'AIOSEOP_VERSION' );
+ $aioseo_v4 = defined( 'AIOSEO_VERSION' ) && function_exists( 'aioseo' );
+
+ if ( ! $aioseo_v3 && ! $aioseo_v4 ) {
+ return $sitemaps;
+ }
+
+ $sitemap_enabled = false;
+ if ( $aioseo_v3 ) {
+ $aioseop_options = get_option( 'aioseop_options' );
+ $sitemap_enabled = ( isset( $aioseop_options['modules']['aiosp_feature_manager_options']['aiosp_feature_manager_enable_sitemap'] ) && 'on' === $aioseop_options['modules']['aiosp_feature_manager_options']['aiosp_feature_manager_enable_sitemap'] ) || ( ! isset( $aioseop_options['modules']['aiosp_feature_manager_options'] ) && isset( $aioseop_options['modules']['aiosp_sitemap_options'] ) );
+ }
+
+ if (
+ ( $aioseo_v3 && ! $sitemap_enabled ) ||
+ ( $aioseo_v4 && ! aioseo()->options->sitemap->general->enable )
+ ) {
+ return $sitemaps;
+ }
+
+ if ( $aioseo_v3 ) {
+ $sitemaps[] = trailingslashit( home_url() ) . apply_filters( 'aiosp_sitemap_filename', 'sitemap' ) . '.xml'; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ } elseif ( $aioseo_v4 ) {
+ $sitemaps[] = trailingslashit( home_url() ) . apply_filters( 'aioseo_sitemap_filename', 'sitemap' ) . '.xml'; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ }
+
+ return $sitemaps;
+ }
+ add_filter( 'rocket_sitemap_preload_list', 'rocket_add_all_in_one_seo_sitemap' );
+
+ /**
+ * Add All in One SEO Sitemap sub-option on WP Rocket settings page
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @param Array $options Array of WP Rocket options.
+ * @return Array Updated array of WP Rocket options
+ */
+ function rocket_sitemap_preload_all_in_one_seo_option( $options ) {
+ $options['all_in_one_seo_xml_sitemap'] = [
+ 'type' => 'checkbox',
+ 'container_class' => [
+ 'wpr-field--children',
+ ],
+ 'label' => __( 'All in One SEO XML sitemap', 'rocket' ),
+ // translators: %s = Name of the plugin.
+ 'description' => sprintf( __( 'We automatically detected the sitemap generated by the %s plugin. You can check the option to preload it.', 'rocket' ), 'All in One SEO' ),
+ 'parent' => 'sitemap_preload',
+ 'section' => 'preload_section',
+ 'page' => 'preload',
+ 'default' => 0,
+ 'sanitize_callback' => 'sanitize_checkbox',
+ ];
+
+ return $options;
+ }
+ add_filter( 'rocket_sitemap_preload_options', 'rocket_sitemap_preload_all_in_one_seo_option' );
+ }
+endif;
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/premium-seo-pack.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/premium-seo-pack.php
new file mode 100644
index 00000000..5460a8b8
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/premium-seo-pack.php
@@ -0,0 +1,30 @@
+id ) {
+ return;
+ }
+
+ // Dequeueing this stylesheet unfreezes WP Rocket.
+ wp_dequeue_style( 'psp-main-style' );
+ }
+ add_action( 'admin_print_styles', 'rocket_dequeue_premium_seo_pack_stylesheet', 11 );
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/rank-math-seo.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/rank-math-seo.php
new file mode 100644
index 00000000..a45c706a
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/rank-math-seo.php
@@ -0,0 +1,91 @@
+
+ */
+
+defined( 'ABSPATH' ) || exit;
+
+// Ealry Bail!!
+if ( ! defined( 'RANK_MATH_FILE' ) || ! \RankMath\Helper::is_module_active( 'sitemap' ) ) {
+ return;
+}
+
+/**
+ * Add sitemap option to WP Rocket settings
+ *
+ * @since 3.2.3
+ *
+ * @param array $options WP Rocket settings array.
+ * @return array Updated WP Rocket settings array
+ */
+function rank_math_rocket_sitemap_preload_option( $options ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ $options['rank_math_xml_sitemap'] = [
+ 'type' => 'checkbox',
+ 'container_class' => [
+ 'wpr-field--children',
+ ],
+ 'label' => __( 'Rank Math XML sitemap', 'rocket' ),
+ // translators: %s = Name of the plugin.
+ 'description' => sprintf( __( 'We automatically detected the sitemap generated by the %s plugin. You can check the option to preload it.', 'rocket' ), 'Rank Math SEO' ),
+ 'parent' => 'sitemap_preload',
+ 'section' => 'preload_section',
+ 'page' => 'preload',
+ 'default' => 0,
+ 'sanitize_callback' => 'sanitize_checkbox',
+ ];
+
+ return $options;
+}
+add_filter( 'rocket_sitemap_preload_options', 'rank_math_rocket_sitemap_preload_option' );
+
+/**
+ * Add sitemap option to WP Rocket default options
+ *
+ * @since 3.2.3
+ *
+ * @param array $options WP Rocket options array.
+ * @return array Updated WP Rocket options array
+ */
+function rank_math_rocket_add_sitemap_option( $options ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ $options['rank_math_xml_sitemap'] = 0;
+
+ return $options;
+}
+add_filter( 'rocket_first_install_options', 'rank_math_rocket_add_sitemap_option' );
+
+/**
+ * Sanitize SEO sitemap option value
+ *
+ * @since 3.2.3
+ *
+ * @param array $inputs WP Rocket inputs array.
+ * @return array Sanitized WP Rocket inputs array
+ */
+function rank_math_rocket_sitemap_option_sanitize( $inputs ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ $inputs['rank_math_xml_sitemap'] = ! empty( $inputs['rank_math_xml_sitemap'] ) ? 1 : 0;
+
+ return $inputs;
+}
+add_filter( 'rocket_inputs_sanitize', 'rank_math_rocket_sitemap_option_sanitize' );
+
+/**
+ * Add SEO sitemap URL to the sitemaps to preload
+ *
+ * @since 3.2.3
+ *
+ * @param array $sitemaps Sitemaps to preload.
+ * @return array Updated Sitemaps to preload
+ */
+function rank_math_rocket_sitemap( $sitemaps ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ if ( get_rocket_option( 'rank_math_xml_sitemap', false ) ) {
+ $sitemaps[] = \RankMath\Sitemap\Router::get_base_url( 'sitemap_index.xml' );
+ }
+
+ return $sitemaps;
+}
+add_filter( 'rocket_sitemap_preload_list', 'rank_math_rocket_sitemap' );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/seopress.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/seopress.php
new file mode 100644
index 00000000..5e4fcf68
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/seopress.php
@@ -0,0 +1,98 @@
+ 'checkbox',
+ 'container_class' => [
+ 'wpr-field--children',
+ ],
+ 'label' => __( 'SEOPress XML sitemap', 'rocket' ),
+ // translators: %s = Name of the plugin.
+ 'description' => sprintf( __( 'We automatically detected the sitemap generated by the %s plugin. You can check the option to preload it.', 'rocket' ), 'SEOPress' ),
+ 'parent' => 'sitemap_preload',
+ 'section' => 'preload_section',
+ 'page' => 'preload',
+ 'default' => 0,
+ 'sanitize_callback' => 'sanitize_checkbox',
+ ];
+
+ return $options;
+ }
+ add_filter( 'rocket_sitemap_preload_options', 'rocket_sitemap_preload_seopress_option' );
+ }
+endif;
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/the-seo-framework.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/the-seo-framework.php
new file mode 100644
index 00000000..d158cdfa
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/the-seo-framework.php
@@ -0,0 +1,153 @@
+loaded ) ) {
+ return;
+ }
+
+ /**
+ * 1. Performs option & other checks.
+ * 2. Checks for conflicting sitemap plugins that might prevent loading.
+ *
+ * These methods cache their output at runtime.
+ *
+ * @link https://github.com/wp-media/wp-rocket/issues/899
+ */
+ if ( $tsf->can_run_sitemap() ) {
+ rocket_add_tsf_sitemap_compat();
+ }
+}
+
+/**
+ * Adds compatibility for the sitemap functionality in The SEO Framework plugin.
+ *
+ * @since 3.2.1
+ * @author Sybre Waaijer
+ */
+function rocket_add_tsf_sitemap_compat() {
+ add_filter( 'rocket_first_install_options', 'rocket_add_tsf_seo_sitemap_option' );
+ add_filter( 'rocket_inputs_sanitize', 'rocket_tsf_seo_sitemap_option_sanitize' );
+ add_filter( 'rocket_sitemap_preload_list', 'rocket_add_tsf_sitemap_to_preload' );
+ add_filter( 'rocket_sitemap_preload_options', 'rocket_sitemap_add_tsf_sitemap_to_preload_option' );
+}
+
+/**
+ * Adds a sitemap option in WP Rocket for The SEO Framework.
+ *
+ * @since 3.2.1
+ * @author Sybre Waaijer
+ * @source ./yoast-seo.php (Remy Perona)
+ *
+ * @param array $options WP Rocket options array.
+ * @return array Updated WP Rocket options array
+ */
+function rocket_add_tsf_seo_sitemap_option( $options ) {
+ $options['tsf_xml_sitemap'] = 0;
+
+ return $options;
+}
+
+/**
+ * Sanitizes the added sitemap option for The SEO Framework.
+ *
+ * @since 3.2.1
+ * @author Sybre Waaijer
+ * @source ./yoast-seo.php (Remy Perona)
+ *
+ * @param array $inputs WP Rocket inputs array.
+ * @return array Sanitized WP Rocket inputs array
+ */
+function rocket_tsf_seo_sitemap_option_sanitize( $inputs ) {
+ $inputs['tsf_xml_sitemap'] = ! empty( $inputs['tsf_xml_sitemap'] ) ? 1 : 0;
+
+ return $inputs;
+}
+
+/**
+ * Adds TSF sitemap URLs to preload.
+ *
+ * @since 3.2.1
+ * @since TODO Added compatibility support for The SEO Framework v4.0+
+ * @author Sybre Waaijer
+ * @source ./yoast-seo.php (Remy Perona)
+ *
+ * @param array $sitemaps Sitemaps to preload.
+ * @return array Updated Sitemaps to preload
+ */
+function rocket_add_tsf_sitemap_to_preload( $sitemaps ) {
+
+ if ( get_rocket_option( 'tsf_xml_sitemap', false ) ) {
+ // The autoloader in TSF doesn't check for file_exists(). So, use version compare instead to prevent fatal errors.
+ if ( version_compare( THE_SEO_FRAMEWORK_VERSION, '4.0', '>=' ) ) {
+ // TSF 4.0+. Expect the class to exist indefinitely.
+
+ $sitemap_bridge = The_SEO_Framework\Bridges\Sitemap::get_instance();
+
+ foreach ( $sitemap_bridge->get_sitemap_endpoint_list() as $id => $data ) {
+ // When the sitemap is good enough for a robots display, we determine it as valid for precaching.
+ // Non-robots display types are among the stylesheet endpoint, or the Yoast SEO-compatible endpoint.
+ // In other words, this enables support for ALL current and future public sitemap endpoints.
+ if ( ! empty( $data['robots'] ) ) {
+ $sitemaps[] = $sitemap_bridge->get_expected_sitemap_endpoint_url( $id );
+ }
+ }
+ } else {
+ // Deprecated. TSF <4.0.
+ $sitemaps[] = the_seo_framework()->get_sitemap_xml_url();
+ }
+ }
+
+ return $sitemaps;
+}
+
+/**
+ * Add The SEO Framework SEO option to WP Rocket settings
+ *
+ * @since 3.2.1
+ * @author Sybre Waaijer
+ * @source ./yoast-seo.php (Remy Perona)
+ *
+ * @param array $options WP Rocket settings array.
+ * @return array Updated WP Rocket settings array
+ */
+function rocket_sitemap_add_tsf_sitemap_to_preload_option( $options ) {
+ $options['tsf_xml_sitemap'] = [
+ 'type' => 'checkbox',
+ 'container_class' => [
+ 'wpr-field--children',
+ ],
+ 'label' => __( 'The SEO Framework XML sitemap', 'rocket' ),
+ // translators: %s = Name of the plugin.
+ 'description' => sprintf( __( 'We automatically detected the sitemap generated by the %s plugin. You can check the option to preload it.', 'rocket' ), 'The SEO Framework' ),
+ 'parent' => 'sitemap_preload',
+ 'section' => 'preload_section',
+ 'page' => 'preload',
+ 'default' => 0,
+ 'sanitize_callback' => 'sanitize_checkbox',
+ ];
+
+ return $options;
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/yoast-seo.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/yoast-seo.php
new file mode 100644
index 00000000..c5316d3a
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/seo/yoast-seo.php
@@ -0,0 +1,99 @@
+= 0 ) {
+ $yoast_seo = get_option( 'wpseo' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ $yoast_seo_xml['enablexmlsitemap'] = isset( $yoast_seo['enable_xml_sitemap'] ) && $yoast_seo['enable_xml_sitemap']; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ }
+
+ /**
+ * Improvement with Yoast SEO: auto-detect the XML sitemaps for the preload option
+ *
+ * @since 2.8
+ * @author Remy Perona
+ */
+ if ( true === $yoast_seo_xml['enablexmlsitemap'] ) {
+ /**
+ * Add Yoast SEO sitemap option to WP Rocket default options
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @param array $options WP Rocket options array.
+ * @return array Updated WP Rocket options array
+ */
+ function rocket_add_yoast_seo_sitemap_option( $options ) {
+ $options['yoast_xml_sitemap'] = 0;
+
+ return $options;
+ }
+ add_filter( 'rocket_first_install_options', 'rocket_add_yoast_seo_sitemap_option' );
+
+ /**
+ * Sanitize Yoast SEO sitemap option value
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @param array $inputs WP Rocket inputs array.
+ * @return array Sanitized WP Rocket inputs array
+ */
+ function rocket_yoast_seo_sitemap_option_sanitize( $inputs ) {
+ $inputs['yoast_xml_sitemap'] = ! empty( $inputs['yoast_xml_sitemap'] ) ? 1 : 0;
+
+ return $inputs;
+ }
+ add_filter( 'rocket_inputs_sanitize', 'rocket_yoast_seo_sitemap_option_sanitize' );
+
+ /**
+ * Add Yoast SEO sitemap URL to the sitemaps to preload
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @param array $sitemaps Sitemaps to preload.
+ * @return array Updated Sitemaps to preload
+ */
+ function rocket_add_yoast_seo_sitemap( $sitemaps ) {
+ if ( get_rocket_option( 'yoast_xml_sitemap', false ) ) {
+ $sitemaps[] = WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' );
+ }
+
+ return $sitemaps;
+ }
+ add_filter( 'rocket_sitemap_preload_list', 'rocket_add_yoast_seo_sitemap' );
+
+ /**
+ * Add Yoast SEO option to WP Rocket settings
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @param array $options WP Rocket settings array.
+ * @return array Updated WP Rocket settings array
+ */
+ function rocket_sitemap_preload_yoast_seo_option( $options ) {
+ $options['yoast_xml_sitemap'] = [
+ 'type' => 'checkbox',
+ 'container_class' => [
+ 'wpr-field--children',
+ ],
+ 'label' => __( 'Yoast SEO XML sitemap', 'rocket' ),
+ // translators: %s = Name of the plugin.
+ 'description' => sprintf( __( 'We automatically detected the sitemap generated by the %s plugin. You can check the option to preload it.', 'rocket' ), 'Yoast SEO' ),
+ 'parent' => 'sitemap_preload',
+ 'section' => 'preload_section',
+ 'page' => 'preload',
+ 'default' => 0,
+ 'sanitize_callback' => 'sanitize_checkbox',
+ ];
+
+ return $options;
+ }
+ add_filter( 'rocket_sitemap_preload_options', 'rocket_sitemap_preload_yoast_seo_option' );
+ }
+endif;
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/slider/meta-slider.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/slider/meta-slider.php
new file mode 100644
index 00000000..5f8c4582
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/slider/meta-slider.php
@@ -0,0 +1,17 @@
+id ) {
+ return;
+ }
+
+ wp_dequeue_style( 'sumome-admin-styles' );
+ wp_dequeue_style( 'sumome-admin-media' );
+ }
+ add_action( 'admin_enqueue_scripts', 'rocket_dequeue_sumo_me_css', PHP_INT_MAX );
+
+ /**
+ * Dequeue SumoMe inline script
+ *
+ * @since 3.0.4
+ * @author Arun Basil Lal
+ */
+ function rocket_dequeue_sumo_me_js() {
+
+ // Retun on all pages but WP Rocket settings page.
+ $screen = get_current_screen();
+ if ( 'settings_page_wprocket' !== $screen->id ) {
+ return;
+ }
+
+ global $wp_plugin_sumome;
+ remove_action( 'admin_footer', [ $wp_plugin_sumome, 'append_admin_script_code' ] );
+ }
+ add_action( 'admin_head', 'rocket_dequeue_sumo_me_js' );
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/thrive-leads.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/thrive-leads.php
new file mode 100644
index 00000000..c0cbf561
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/thrive-leads.php
@@ -0,0 +1,13 @@
+ 0;
+}
+add_filter( 'rocket_override_donotcachepage', 'rocket_override_donotcachepage_on_thrive_leads' );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/varnish-http-purge.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/varnish-http-purge.php
new file mode 100644
index 00000000..e94e43ee
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/varnish-http-purge.php
@@ -0,0 +1,75 @@
+ 'PURGE',
+ 'blocking' => false,
+ 'headers' => [
+ 'host' => $p['host'],
+ 'X-Purge-Method' => 'regex',
+ ],
+ ]
+ );
+
+ do_action( 'after_purge_url', $url, $purgeme ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-offload-s3-assets.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-offload-s3-assets.php
new file mode 100644
index 00000000..189b9ec8
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-offload-s3-assets.php
@@ -0,0 +1,38 @@
+is_plugin_setup() && 1 === (int) $as3cf_assets->get_setting( 'enable-addon' ) ) {
+ // Disable WP Rocket CDN option.
+ add_filter( 'rocket_readonly_cdn_option', '__return_true' );
+ }
+}
+
+/**
+ * Deactivate WP Rocket CDN if WP Offload S3 assets addon copy & serve is active.
+ *
+ * @since 2.10.7
+ * @author Remy Perona
+ *
+ * @param string $old_value Previous assets option value.
+ * @param string $new_value New assets option value.
+ */
+function rocket_maybe_deactivate_cdn( $old_value, $new_value ) {
+ if ( $old_value['enable-addon'] !== $new_value['enable-addon'] && 1 === (int) $new_value['enable-addon'] ) {
+ update_rocket_option( 'cdn', 0 );
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-offload-s3.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-offload-s3.php
new file mode 100644
index 00000000..4b67e636
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-offload-s3.php
@@ -0,0 +1,23 @@
+is_plugin_setup() && 1 === (int) $as3cf->get_setting( 'serve-from-s3' ) ) {
+ // Remove images option from WP Rocket CDN dropdown settings.
+ add_filter( 'rocket_allow_cdn_images', '__return_false' );
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-print.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-print.php
new file mode 100644
index 00000000..fda86ef5
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-print.php
@@ -0,0 +1,19 @@
+query_vars['print'] ) ) {
+ add_filter( 'do_rocket_lazyload', '__return_false' );
+ }
+ }
+ add_action( 'wp', 'rocket_deactivate_lazyload_on_print_pages' );
+endif;
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-rest-api.php b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-rest-api.php
new file mode 100644
index 00000000..c1bef7da
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/plugins/wp-rest-api.php
@@ -0,0 +1,48 @@
+ example.com/blog/category/wp-json/).
+ */
+ $prefix = rocket_is_subfolder_install() ? '(/[^/]+)?' : '';
+ $index = ! empty( $wp_rewrite->index ) ? $wp_rewrite->index : 'index.php';
+ $index = preg_quote( $index, '/' );
+ $suffix = rest_get_url_prefix();
+ $suffix = preg_quote( trim( $suffix, '/' ), '/' );
+
+ /**
+ * Results in:
+ * - Single site: (/index\.php)?/wp\-json(/.*|$)
+ * - Multisite: (/[^/]+)?(/index\.php)?/wp\-json(/.*|$)
+ */
+ $uri[] = $prefix . '/(' . $index . '/)?' . $suffix . '(/.*|$)';
+
+ return $uri;
+}
+add_filter( 'rocket_cache_reject_uri', 'rocket_exclude_wp_rest_api' );
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/themes/studiopress.php b/wp-content/plugins/wp-rocket/inc/3rd-party/themes/studiopress.php
new file mode 100644
index 00000000..b9dfb76d
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/themes/studiopress.php
@@ -0,0 +1,44 @@
+cache_flush_theme();
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/3rd-party/themes/uncode.php b/wp-content/plugins/wp-rocket/inc/3rd-party/themes/uncode.php
new file mode 100644
index 00000000..0fd6aea3
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/3rd-party/themes/uncode.php
@@ -0,0 +1,63 @@
+get( 'Name' ) ) || 'uncode' === strtolower( $current_theme->get( 'Template' ) ) ) {
+ /**
+ * Excludes Uncode init and ai-uncode JS files from minification/combine
+ *
+ * @since 3.1
+ * @author Remy Perona
+ *
+ * @param array $excluded_js Array of JS filepaths to be excluded.
+ * @return array
+ */
+ function rocket_exclude_js_uncode( $excluded_js ) {
+ $excluded_js[] = rocket_clean_exclude_file( get_template_directory_uri() . '/library/js/init.js' );
+ $excluded_js[] = rocket_clean_exclude_file( get_template_directory_uri() . '/library/js/min/init.min.js' );
+ $excluded_js[] = rocket_clean_exclude_file( get_template_directory_uri() . '/library/js/ai-uncode.js' );
+ $excluded_js[] = rocket_clean_exclude_file( get_template_directory_uri() . '/library/js/min/ai-uncode.min.js' );
+
+ return $excluded_js;
+ }
+ add_filter( 'rocket_exclude_js', 'rocket_exclude_js_uncode' );
+
+ /**
+ * Excludes some Uncode inline scripts from combine JS
+ *
+ * @since 3.1
+ * @author Remy Perona
+ *
+ * @param array $inline_js Array of patterns to match for exclusion.
+ * @return array
+ */
+ function rocket_exclude_inline_js_uncode( $inline_js ) {
+ $inline_js[] = 'SiteParameters';
+ $inline_js[] = 'script-';
+ $inline_js[] = 'initBox';
+ $inline_js[] = 'initHeader';
+ $inline_js[] = 'fixMenuHeight';
+
+ return $inline_js;
+ }
+ add_filter( 'rocket_excluded_inline_js_content', 'rocket_exclude_inline_js_uncode' );
+
+ /**
+ * Excludes Uncode JS files from defer JS
+ *
+ * @since 3.2.5
+ * @author Remy Perona
+ *
+ * @param array $exclude_defer_js Array of JS filepaths to be excluded.
+ * @return array
+ */
+ function rocket_exclude_defer_js_uncode( $exclude_defer_js ) {
+ $exclude_defer_js[] = rocket_clean_exclude_file( get_template_directory_uri() . '/library/js/init.js' );
+ $exclude_defer_js[] = rocket_clean_exclude_file( get_template_directory_uri() . '/library/js/min/init.min.js' );
+ return $exclude_defer_js;
+ }
+ add_filter( 'rocket_exclude_defer_js', 'rocket_exclude_defer_js_uncode' );
+
+}
diff --git a/wp-content/plugins/wp-rocket/inc/API/bypass.php b/wp-content/plugins/wp-rocket/inc/API/bypass.php
new file mode 100644
index 00000000..39645968
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/API/bypass.php
@@ -0,0 +1,31 @@
+query_vars, home_url( $wp->request ) ) );
+ $bypass = isset( $url['query'] ) && false !== strpos( $url['query'], 'nowprocket' );
+
+ return $bypass;
+}
diff --git a/wp-content/plugins/wp-rocket/inc/API/preload.php b/wp-content/plugins/wp-rocket/inc/API/preload.php
new file mode 100644
index 00000000..d0e611c5
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/API/preload.php
@@ -0,0 +1,115 @@
+preload( $urls );
+
+ return true;
+}
+
+/**
+ * Launches the sitemap preload (helper function for backward compatibility)
+ *
+ * @since 2.8
+ * @author Remy Perona
+ *
+ * @return void
+ */
+function run_rocket_sitemap_preload() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ if ( ! get_rocket_option( 'sitemap_preload' ) || ! get_rocket_option( 'manual_preload' ) ) {
+ return;
+ }
+
+ /**
+ * Filters the sitemaps list to preload
+ *
+ * @since 2.8
+ *
+ * @param array Array of sitemaps URL
+ */
+ $sitemaps = apply_filters( 'rocket_sitemap_preload_list', get_rocket_option( 'sitemaps', false ) );
+ $sitemaps = array_flip( array_flip( $sitemaps ) );
+
+ if ( ! $sitemaps ) {
+ return;
+ }
+
+ $sitemap_preload = new Sitemap( new FullProcess() );
+
+ $sitemap_preload->run_preload( $sitemaps );
+}
+
+/**
+ * Launches the preload cache from the admin bar or the dashboard button
+ *
+ * @since 1.3.0 Compatibility with WPML
+ * @since 1.0 (delete in 1.1.6 and re-add in 1.1.9)
+ * @deprecated 3.2
+ */
+function do_admin_post_rocket_preload_cache() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ if ( empty( $_GET['_wpnonce'] ) ) {
+ wp_safe_redirect( wp_get_referer() );
+ die();
+ }
+
+ if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'preload' ) ) {
+ wp_nonce_ays( '' );
+ }
+
+ if ( ! current_user_can( 'rocket_preload_cache' ) ) {
+ wp_safe_redirect( wp_get_referer() );
+ die();
+ }
+
+ $preload_process = new FullProcess();
+
+ if ( $preload_process->is_process_running() ) {
+ wp_safe_redirect( wp_get_referer() );
+ die();
+ }
+
+ delete_transient( 'rocket_preload_errors' );
+
+ $lang = isset( $_GET['lang'] ) && 'all' !== $_GET['lang'] ? sanitize_key( $_GET['lang'] ) : '';
+ run_rocket_bot( 'cache-preload', $lang );
+ run_rocket_sitemap_preload();
+
+ if ( ! strpos( wp_get_referer(), 'wprocket' ) ) {
+ set_transient( 'rocket_preload_triggered', 1 );
+ }
+
+ wp_safe_redirect( wp_get_referer() );
+ die();
+}
+add_action( 'admin_post_nopriv_preload', 'do_admin_post_rocket_preload_cache' );
+add_action( 'admin_post_preload', 'do_admin_post_rocket_preload_cache' );
diff --git a/wp-content/plugins/wp-rocket/inc/Addon/Busting/BustingFactory.php b/wp-content/plugins/wp-rocket/inc/Addon/Busting/BustingFactory.php
new file mode 100644
index 00000000..366e3488
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/Addon/Busting/BustingFactory.php
@@ -0,0 +1,58 @@
+busting_path = $busting_path;
+ $this->busting_url = $busting_url;
+ }
+
+ /**
+ * Creator method
+ *
+ * @param string $type Type of busting class to create.
+ * @return Busting_Interface
+ */
+ public function type( $type ) {
+ switch ( $type ) {
+ case 'fbpix':
+ return new Facebook_Pickles( $this->busting_path, $this->busting_url );
+ case 'fbsdk':
+ return new Facebook_SDK( $this->busting_path, $this->busting_url );
+ case 'ga':
+ return new GoogleAnalytics( $this->busting_path, $this->busting_url );
+ case 'gtm':
+ return new GoogleTagManager( $this->busting_path, $this->busting_url, new GoogleAnalytics( $this->busting_path, $this->busting_url ) );
+ }
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/Addon/Busting/FileBustingTrait.php b/wp-content/plugins/wp-rocket/inc/Addon/Busting/FileBustingTrait.php
new file mode 100644
index 00000000..b58f7677
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/Addon/Busting/FileBustingTrait.php
@@ -0,0 +1,478 @@
+get_busting_version() ) {
+ // We have a local copy.
+ Logger::debug(
+ 'Found local file.',
+ [
+ self::LOGGER_CONTEXT,
+ 'path' => $this->get_busting_path(),
+ ]
+ );
+ return true;
+ }
+
+ if ( $this->refresh_save( $url ) ) {
+ // We downloaded a fresh copy.
+ Logger::debug(
+ 'New copy downloaded.',
+ [
+ self::LOGGER_CONTEXT,
+ 'path' => $this->get_busting_path(),
+ ]
+ );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Deletes the busting file.
+ *
+ * @since 3.1
+ * @since 3.2.4 Handle versioning.
+ * @access public
+ * @author Remy Perona
+ * @author Grégory Viguier
+ *
+ * @return bool True on success. False on failure.
+ */
+ public function delete() {
+ $files = $this->get_all_files();
+
+ if ( false === $files ) {
+ // Error.
+ return false;
+ }
+
+ $this->file_version = null;
+
+ if ( ! $files ) {
+ // No local files yet.
+ return true;
+ }
+
+ return $this->delete_files( array_keys( $files ) );
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** LOCAL FILE ============================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get the version of the current busting file.
+ *
+ * @since 3.2.4
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string|bool Version of the file. False if the file does not exist.
+ */
+ protected function get_busting_version() {
+ if ( ! empty( $this->file_version ) ) {
+ return $this->file_version;
+ }
+
+ $files = $this->get_all_files();
+
+ if ( ! $files ) {
+ // Error or no local files yet.
+ return false;
+ }
+
+ // Since we're not supposed to have several files, return the first one.
+ $this->file_version = reset( $files );
+
+ return $this->file_version;
+ }
+
+ /**
+ * Get all cached files in the directory.
+ * In a perfect world, there should be only one.
+ *
+ * @since 3.2.4
+ * @access private
+ *
+ * @return bool|array A list of file names (as array keys) and versions (as array values). False on failure.
+ */
+ private function get_all_files() {
+ $dir_path = rtrim( $this->busting_path, '\\/' );
+
+ if ( ! $this->filesystem->exists( $dir_path ) ) {
+ return [];
+ }
+
+ if ( ! $this->filesystem->is_readable( $dir_path ) ) {
+ Logger::error(
+ 'Directory is not readable.',
+ [
+ self::LOGGER_CONTEXT,
+ 'path' => $dir_path,
+ ]
+ );
+ return false;
+ }
+
+ $pattern = '/' . sprintf(
+ $this->escape_file_name( $this->filename_pattern ),
+ '([a-f0-9]{32}|local)'
+ ) . '/';
+
+ $entries = _rocket_get_dir_files_by_regex( $dir_path, $pattern );
+
+ $list = [];
+ foreach ( $entries as $entry ) {
+ $filename = $entry->getFilename();
+
+ preg_match( $pattern, $filename, $file_details_match );
+ if ( ! empty( $file_details_match[1] ) ) {
+ $list[ $filename ] = $file_details_match[1];
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Get the final URL for the current cache busting file.
+ *
+ * @since 3.2.4
+ * @access protected
+ *
+ * @return string|bool URL of the file. False if the file does not exist.
+ */
+ public function get_busting_url() {
+ return $this->get_busting_file_url( $this->get_busting_version() );
+ }
+
+ /**
+ * Get the path to the current cache busting file.
+ *
+ * @since 3.2.4
+ * @access protected
+ * @author Grégory Viguier
+ *
+ * @return string|bool URL of the file. False if the file does not exist.
+ */
+ protected function get_busting_path() {
+ return $this->get_busting_file_path( $this->get_busting_version() );
+ }
+
+ /**
+ * Get the final URL for a cache busting file.
+ *
+ * @since 3.2.4
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param string $version The file version.
+ * @return string|bool URL of the file with this version. False if no versions are provided.
+ */
+ private function get_busting_file_url( $version ) {
+ if ( ! $version ) {
+ return false;
+ }
+
+ $filename = $this->get_busting_file_name( $version );
+
+ // This filter is documented in inc/functions/minify.php.
+ return apply_filters( 'rocket_js_url', $this->busting_url . $filename );
+ }
+
+ /**
+ * Get the local file name.
+ *
+ * @since 3.2.4
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param string $version The file version.
+ * @return string|bool The name of the file with this version. False if no versions are provided.
+ */
+ private function get_busting_file_name( $version ) {
+ if ( ! $version ) {
+ return false;
+ }
+
+ return sprintf( $this->filename_pattern, $version );
+ }
+
+ /**
+ * Get the local file path.
+ *
+ * @since 3.2.4
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param string $version The file version.
+ * @return string|bool Path to the file with this version. False if no versions are provided.
+ */
+ private function get_busting_file_path( $version ) {
+ if ( ! $version ) {
+ return false;
+ }
+
+ return $this->busting_path . $this->get_busting_file_name( $version );
+ }
+
+ /**
+ * Escape a file name, to be used in a regex pattern (delimiter is `/`).
+ * `%s` conversion specifications are protected.
+ *
+ * @since 3.2.4
+ * @access private
+ *
+ * @param string $filename_pattern The file name.
+ * @return string
+ */
+ private function escape_file_name( $filename_pattern ) {
+ return preg_quote( $filename_pattern, '/' );
+ }
+
+ /**
+ * Delete busting files.
+ *
+ * @since 3.2.4
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param array $files A list of file names.
+ * @return bool True if files have been deleted (or no files have been provided). False on failure.
+ */
+ private function delete_files( $files ) {
+ if ( ! $files ) {
+ // ¯\_(ã)_/¯
+ return true;
+ }
+
+ $has_deleted = false;
+ $error_paths = [];
+
+ foreach ( $files as $file_name ) {
+ if ( ! $this->filesystem->delete( $this->busting_path . $file_name, false, 'f' ) ) {
+ $error_paths[] = $this->busting_path . $file_name;
+ } else {
+ $has_deleted = true;
+ }
+ }
+
+ if ( $error_paths ) {
+ // Group all deletion errors into one log.
+ Logger::error(
+ 'Local file(s) could not be deleted.',
+ [
+ self::LOGGER_CONTEXT,
+ 'paths' => $error_paths,
+ ]
+ );
+ }
+
+ return $has_deleted;
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** UPDATE THE LOCAL FILE =================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Add new contents to a file. If the file doesn't exist, it is created.
+ *
+ * @since 3.2.4
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file to update.
+ * @param string $file_contents New contents.
+ * @return string|bool The file contents on success. False on failure.
+ */
+ private function update_file_contents( $file_path, $file_contents ) {
+ if ( ! $this->is_busting_dir_writable() ) {
+ return false;
+ }
+
+ if ( ! rocket_put_content( $file_path, $file_contents ) ) {
+ Logger::error(
+ 'Contents could not be written into file.',
+ [
+ self::LOGGER_CONTEXT,
+ 'path' => $file_path,
+ ]
+ );
+ return false;
+ }
+
+ return $file_contents;
+ }
+
+ /**
+ * Tell if the directory containing the busting file is writable.
+ *
+ * @since 3.2
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @return bool
+ */
+ private function is_busting_dir_writable() {
+ if ( ! $this->filesystem->exists( $this->busting_path ) ) {
+ rocket_mkdir_p( $this->busting_path );
+ }
+
+ if ( ! $this->filesystem->is_writable( $this->busting_path ) ) {
+ Logger::error(
+ 'Directory is not writable.',
+ [
+ self::LOGGER_CONTEXT,
+ 'paths' => $this->busting_path,
+ ]
+ );
+ return false;
+ }
+
+ return true;
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** GET LOCAL/REMOTE CONTENTS =============================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Get a file contents. If the file doesn't exist, new contents are fetched remotely.
+ *
+ * @since 3.2.4
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @param string $file_url URL to the remote file.
+ * @return string|bool The contents on success, false on failure.
+ */
+ private function get_file_or_remote_contents( $file_path, $file_url ) {
+ $content = $this->get_file_contents( $file_path );
+
+ if ( $content ) {
+ // We have a local file.
+ return $content;
+ }
+
+ return $this->get_remote_contents( $file_url );
+ }
+
+ /**
+ * Get a file contents.
+ *
+ * @since 3.2.4
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param string $file_path Path to the file.
+ * @return string|bool The contents on success, false on failure.
+ */
+ private function get_file_contents( $file_path ) {
+ if ( ! $this->filesystem->exists( $file_path ) ) {
+ Logger::error(
+ 'Local file does not exist.',
+ [
+ self::LOGGER_CONTEXT,
+ 'path' => $file_path,
+ ]
+ );
+ return false;
+ }
+
+ if ( ! $this->filesystem->is_readable( $file_path ) ) {
+ Logger::error(
+ 'Local file is not readable.',
+ [
+ self::LOGGER_CONTEXT,
+ 'path' => $file_path,
+ ]
+ );
+ return false;
+ }
+
+ $content = $this->filesystem->get_contents( $file_path );
+
+ if ( ! $content ) {
+ Logger::error(
+ 'Local file is empty.',
+ [
+ self::LOGGER_CONTEXT,
+ 'path' => $file_path,
+ ]
+ );
+ return false;
+ }
+
+ return $content;
+ }
+
+ /**
+ * Get the contents of a URL.
+ *
+ * @since 3.2.4
+ * @access private
+ * @author Grégory Viguier
+ *
+ * @param string $url The URL to request.
+ * @return string|bool The contents on success. False on failure.
+ */
+ private function get_remote_contents( $url ) {
+ try {
+ $response = wp_remote_get( $url );
+ } catch ( Exception $e ) {
+ Logger::error(
+ 'Remote file could not be fetched.',
+ [
+ self::LOGGER_CONTEXT,
+ 'url' => $url,
+ 'response' => $e->getMessage(),
+ ]
+ );
+ return false;
+ }
+
+ if ( is_wp_error( $response ) ) {
+ Logger::error(
+ 'Remote file could not be fetched.',
+ [
+ self::LOGGER_CONTEXT,
+ 'url' => $url,
+ 'response' => $response->get_error_message(),
+ ]
+ );
+ return false;
+ }
+
+ $contents = wp_remote_retrieve_body( $response );
+
+ if ( ! $contents ) {
+ Logger::error(
+ 'Remote file could not be fetched.',
+ [
+ self::LOGGER_CONTEXT,
+ 'url' => $url,
+ 'response' => $response,
+ ]
+ );
+ return false;
+ }
+
+ return $contents;
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/APIClient.php b/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/APIClient.php
new file mode 100644
index 00000000..244cad58
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/APIClient.php
@@ -0,0 +1,394 @@
+args = [
+ 'timeout' => 30, // Increase from default of 5 to give extra time for the plugin to process story for exporting.
+ 'sslverify' => true,
+ 'body' => [],
+ ];
+ $this->headers = [
+ 'X-Auth-Email' => '',
+ 'X-Auth-Key' => '',
+ 'User-Agent' => $useragent,
+ 'Content-type' => 'application/json',
+ ];
+ }
+
+ /**
+ * Sets up the API credentials.
+ *
+ * @since 1.0
+ *
+ * @param string $email The email associated with the Cloudflare account.
+ * @param string $api_key The API key for the associated Cloudflare account.
+ * @param string $zone_id The zone ID.
+ */
+ public function set_api_credentials( $email, $api_key, $zone_id ) {
+ $this->email = $email;
+ $this->api_key = $api_key;
+ $this->zone_id = $zone_id;
+
+ $this->headers['X-Auth-Email'] = $email;
+ $this->headers['X-Auth-Key'] = $api_key;
+ }
+
+ /**
+ * Get zone data.
+ *
+ * @since 1.0
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function get_zones() {
+ return $this->get( "zones/{$this->zone_id}" );
+ }
+
+ /**
+ * Get the zone's page rules.
+ *
+ * @since 1.0
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function list_pagerules() {
+ return $this->get( "zones/{$this->zone_id}/pagerules?status=active" );
+ }
+
+ /**
+ * Purges the cache.
+ *
+ * @since 1.0
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function purge() {
+ return $this->delete( "zones/{$this->zone_id}/purge_cache", [ 'purge_everything' => true ] );
+ }
+
+ /**
+ * Purges the given URLs.
+ *
+ * @since 1.0
+ *
+ * @param array|null $urls An array of URLs that should be removed from cache.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function purge_files( array $urls ) {
+ return $this->delete( "zones/{$this->zone_id}/purge_cache", [ 'files' => $urls ] );
+ }
+
+ /**
+ * Changes the zone's browser cache TTL setting.
+ *
+ * @since 1.0
+ *
+ * @param string $value New setting's value.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function change_browser_cache_ttl( $value ) {
+ return $this->change_setting( 'browser_cache_ttl', $value );
+ }
+
+ /**
+ * Changes the zone's rocket loader setting.
+ *
+ * @since 1.0
+ *
+ * @param string $value New setting's value.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function change_rocket_loader( $value ) {
+ return $this->change_setting( 'rocket_loader', $value );
+ }
+
+ /**
+ * Changes the zone's minify setting.
+ *
+ * @since 1.0
+ *
+ * @param string $value New setting's value.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function change_minify( $value ) {
+ return $this->change_setting( 'minify', $value );
+ }
+
+ /**
+ * Changes the zone's cache level.
+ *
+ * @since 1.0
+ *
+ * @param string $value New setting's value.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function change_cache_level( $value ) {
+ return $this->change_setting( 'cache_level', $value );
+ }
+
+ /**
+ * Changes the zone's development mode.
+ *
+ * @since 1.0
+ *
+ * @param string $value New setting's value.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function change_development_mode( $value ) {
+ return $this->change_setting( 'development_mode', $value );
+ }
+
+ /**
+ * Changes the given setting.
+ *
+ * @since 1.0
+ *
+ * @param string $setting Name of the setting to change.
+ * @param string $value New setting's value.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ protected function change_setting( $setting, $value ) {
+ return $this->patch( "zones/{$this->zone_id}/settings/{$setting}", [ 'value' => $value ] );
+ }
+
+ /**
+ * Gets all of the Cloudflare settings.
+ *
+ * @since 1.0
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function get_settings() {
+ return $this->get( "zones/{$this->zone_id}/settings" );
+ }
+
+ /**
+ * Gets Cloudflare's IPs.
+ *
+ * @since 1.0
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ public function get_ips() {
+ return $this->get( '/ips' );
+ }
+
+ /**
+ * API call method for sending requests using GET.
+ *
+ * @since 1.0
+ *
+ * @param string $path Path of the endpoint.
+ * @param array $data Data to be sent along with the request.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ protected function get( $path, array $data = [] ) {
+ return $this->request( $path, $data, 'get' );
+ }
+
+ /**
+ * API call method for sending requests using DELETE.
+ *
+ * @since 1.0
+ *
+ * @param string $path Path of the endpoint.
+ * @param array $data Data to be sent along with the request.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ protected function delete( $path, array $data = [] ) {
+ return $this->request( $path, $data, 'delete' );
+ }
+
+ /**
+ * API call method for sending requests using PATCH.
+ *
+ * @since 1.0
+ *
+ * @param string $path Path of the endpoint.
+ * @param array $data Data to be sent along with the request.
+ *
+ * @return stdClass Cloudflare response packet.
+ */
+ protected function patch( $path, array $data = [] ) {
+ return $this->request( $path, $data, 'patch' );
+ }
+
+ /**
+ * API call method for sending requests using GET, POST, PUT, DELETE OR PATCH.
+ *
+ * @since 1.0
+ *
+ * @author James Bell - credit for original code adapted for version 1.0.
+ * @author WP Media
+ *
+ * @param string $path Path of the endpoint.
+ * @param array $data Data to be sent along with the request.
+ * @param string $method Type of method that should be used ('GET', 'DELETE', 'PATCH').
+ *
+ * @return stdClass response object.
+ * @throws AuthenticationException When email or api key are not set.
+ * @throws UnauthorizedException When Cloudflare's API returns a 401 or 403.
+ */
+ protected function request( $path, array $data = [], $method = 'get' ) {
+ if ( '/ips' !== $path && ! $this->is_authorized() ) {
+ throw new AuthenticationException( 'Authentication information must be provided.' );
+ }
+
+ $response = $this->do_remote_request( $path, $data, $method );
+
+ if ( is_wp_error( $response ) ) {
+ throw new Exception( $response->get_error_message() );
+ }
+
+ $data = wp_remote_retrieve_body( $response );
+
+ if ( empty( $data ) ) {
+ throw new Exception( __( 'Cloudflare did not provide any reply. Please try again later.', 'rocket' ) );
+ }
+
+ $data = json_decode( $data );
+
+ if ( empty( $data->success ) ) {
+ $errors = [];
+ foreach ( $data->errors as $error ) {
+ if ( 6003 === $error->code || 9103 === $error->code ) {
+ $msg = __( 'Incorrect Cloudflare email address or API key.', 'rocket' );
+
+ $msg .= ' ' . sprintf(
+ /* translators: %1$s = opening link; %2$s = closing link */
+ __( 'Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
+ // translators: Documentation exists in EN, FR; use localized URL if applicable.
+ '',
+ ' '
+ );
+
+ throw new Exception( $msg );
+ }
+ if ( 7003 === $error->code ) {
+ $msg = __( 'Incorrect Cloudflare Zone ID.', 'rocket' );
+
+ $msg .= ' ' . sprintf(
+ /* translators: %1$s = opening link; %2$s = closing link */
+ __( 'Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
+ // translators: Documentation exists in EN, FR; use localized URL if applicable.
+ '',
+ ' '
+ );
+
+ throw new Exception( $msg );
+ }
+ $errors[] = $error->message;
+ }
+ throw new Exception( wp_sprintf_l( '%l ', $errors ) );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Checks if the email and API key for the API credentials are set.
+ *
+ * @since 1.0
+ *
+ * @return bool true if authorized; else false.
+ */
+ private function is_authorized() {
+ return (
+ isset( $this->email, $this->api_key )
+ &&
+ false !== filter_var( $this->email, FILTER_VALIDATE_EMAIL )
+ );
+ }
+
+ /**
+ * Does the request remote cURL request.
+ *
+ * @since 1.0
+ *
+ * @param string $path Path of the endpoint.
+ * @param array $data Data to be sent along with the request.
+ * @param string $method Type of method that should be used ('GET', 'DELETE', 'PATCH').
+ *
+ * @return array curl response packet.
+ */
+ private function do_remote_request( $path, array $data, $method ) {
+ $this->args['method'] = isset( $method ) ? strtoupper( $method ) : 'GET';
+
+ if ( '/ips' !== $path ) {
+ $this->args['headers'] = $this->headers;
+ }
+
+ $this->args['body'] = [];
+
+ if ( ! empty( $data ) ) {
+ $this->args['body'] = wp_json_encode( $data );
+ }
+
+ $response = wp_remote_request( self::CLOUDFLARE_API . $path, $this->args );
+
+ return $response;
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/AuthenticationException.php b/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/AuthenticationException.php
new file mode 100644
index 00000000..2398ad65
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/AuthenticationException.php
@@ -0,0 +1,7 @@
+options = $options;
+ $this->cloudflare_api_error = null;
+ $this->api = $api;
+ // Update api_error with WP_Error if credentials are not valid.
+ // Update API with Cloudflare instance with correct auth data.
+ $this->get_cloudflare_instance();
+ }
+
+ /**
+ * Get a Cloudflare\Api instance & the zone_id corresponding to the domain.
+ *
+ * @since 1.0
+ *
+ * @return Object Cloudflare instance & zone_id if credentials are correct, WP_Error otherwise.
+ */
+ public function get_cloudflare_instance() {
+ $cf_email = $this->options->get( 'cloudflare_email', null );
+ $cf_api_key = defined( 'WP_ROCKET_CF_API_KEY' ) ? WP_ROCKET_CF_API_KEY : $this->options->get( 'cloudflare_api_key', null );
+ $cf_zone_id = $this->options->get( 'cloudflare_zone_id', null );
+ $is_api_keys_valid_cf = get_transient( 'rocket_cloudflare_is_api_keys_valid' );
+
+ if ( false === $is_api_keys_valid_cf ) {
+ $is_api_keys_valid_cf = $this->is_api_keys_valid( $cf_email, $cf_api_key, $cf_zone_id );
+ set_transient( 'rocket_cloudflare_is_api_keys_valid', $is_api_keys_valid_cf, 2 * WEEK_IN_SECONDS );
+ }
+
+ if ( is_wp_error( $is_api_keys_valid_cf ) ) {
+ // Sets Cloudflare API as WP_Error if credentials are not valid.
+ $this->cloudflare_api_error = $is_api_keys_valid_cf;
+
+ return;
+ }
+
+ // Sets Cloudflare Valid Credentials and User Agent.
+ $this->api->set_api_credentials( $cf_email, $cf_api_key, $cf_zone_id );
+ }
+
+ /**
+ * Validate Cloudflare input data.
+ *
+ * @since 1.0
+ *
+ * @param string $cf_email Cloudflare email.
+ * @param string $cf_api_key Cloudflare API key.
+ * @param string $cf_zone_id Cloudflare zone ID.
+ *
+ * @return stdClass true if credentials are ok, WP_Error otherwise.
+ */
+ public function is_api_keys_valid( $cf_email, $cf_api_key, $cf_zone_id ) {
+ if ( empty( $cf_email ) || empty( $cf_api_key ) ) {
+ return new WP_Error(
+ 'cloudflare_credentials_empty',
+ sprintf(
+ /* translators: %1$s = opening link; %2$s = closing link */
+ __( 'Cloudflare email and/or API key are not set. Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
+ // translators: Documentation exists in EN, FR; use localized URL if applicable.
+ '',
+ ' '
+ )
+ );
+ }
+
+ if ( empty( $cf_zone_id ) ) {
+ $msg = __( 'Missing Cloudflare Zone ID.', 'rocket' );
+
+ $msg .= ' ' . sprintf(
+ /* translators: %1$s = opening link; %2$s = closing link */
+ __( 'Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
+ // translators: Documentation exists in EN, FR; use localized URL if applicable.
+ '',
+ ' '
+ );
+
+ return new WP_Error( 'cloudflare_no_zone_id', $msg );
+ }
+
+ try {
+ $this->api->set_api_credentials( $cf_email, $cf_api_key, $cf_zone_id );
+
+ $cf_zone = $this->api->get_zones();
+ $zone_found = false;
+ $site_url = get_site_url();
+
+ if ( function_exists( 'domain_mapping_siteurl' ) ) {
+ $site_url = domain_mapping_siteurl( $site_url );
+ }
+
+ if ( ! empty( $cf_zone->result ) ) {
+ $parsed_url = wp_parse_url( $site_url );
+ if ( false !== strpos( strtolower( $parsed_url['host'] ), $cf_zone->result->name ) ) {
+ $zone_found = true;
+ }
+ }
+
+ if ( ! $zone_found ) {
+ $msg = __( 'It looks like your domain is not set up on Cloudflare.', 'rocket' );
+
+ $msg .= ' ' . sprintf(
+ /* translators: %1$s = opening link; %2$s = closing link */
+ __( 'Read the %1$sdocumentation%2$s for further guidance.', 'rocket' ),
+ // translators: Documentation exists in EN, FR; use localized URL if applicable.
+ '',
+ ' '
+ );
+
+ return new WP_Error( 'cloudflare_wrong_zone_id', $msg );
+ }
+
+ $this->cloudflare_api_error = null;
+ return true;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_invalid_auth', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Checks if CF has the $action_value set as a Page Rule.
+ *
+ * @since 1.0
+ *
+ * @param string $action_value Cache_everything.
+ *
+ * @return mixed Object|bool true / false if $action_value was found or not, WP_Error otherwise.
+ */
+ public function has_page_rule( $action_value ) {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ try {
+ $cf_page_rule = $this->api->list_pagerules();
+ $cf_page_rule_arr = wp_json_encode( $cf_page_rule );
+
+ return preg_match( '/' . $action_value . '/', $cf_page_rule_arr );
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_page_rule_failed', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Purge Cloudflare cache.
+ *
+ * @since 1.0
+ *
+ * @return mixed Object|bool true if the purge is successful, WP_Error otherwise.
+ */
+ public function purge_cloudflare() {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ try {
+ $cf_purge = $this->api->purge();
+ return true;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_purge_failed', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Purge Cloudflare Cache by URL
+ *
+ * @since 1.0
+ *
+ * @param WP_Post $post The post object.
+ * @param array $purge_urls URLs cache files to remove.
+ * @param string $lang The post language.
+ *
+ * @return mixed Object|bool true if the purge is successful, WP_Error otherwise
+ */
+ public function purge_by_url( $post, $purge_urls, $lang ) {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ try {
+ $cf_purge = $this->api->purge_files( $purge_urls );
+ return true;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_purge_failed', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Set the Browser Cache TTL in Cloudflare.
+ *
+ * @since 1.0
+ *
+ * @param string $mode Value for Cloudflare browser cache TTL.
+ *
+ * @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
+ */
+ public function set_browser_cache_ttl( $mode ) {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ try {
+ $cf_return = $this->api->change_browser_cache_ttl( (int) $mode );
+ return $mode;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_browser_cache', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Set the Cloudflare Rocket Loader.
+ *
+ * @since 1.0
+ *
+ * @param string $mode Value for Cloudflare Rocket Loader.
+ *
+ * @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
+ */
+ public function set_rocket_loader( $mode ) {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ try {
+ $cf_return = $this->api->change_rocket_loader( $mode );
+ return $mode;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_rocket_loader', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Set the Cloudflare Minification.
+ *
+ * @since 1.0
+ *
+ * @param string $mode Value for Cloudflare minification.
+ *
+ * @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
+ */
+ public function set_minify( $mode ) {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ $cf_minify_settings = [
+ 'css' => $mode,
+ 'html' => $mode,
+ 'js' => $mode,
+ ];
+
+ try {
+ $cf_return = $this->api->change_minify( $cf_minify_settings );
+ return $mode;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_minification', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Set the Cloudflare Caching level.
+ *
+ * @since 1.0
+ *
+ * @param string $mode Value for Cloudflare caching level.
+ *
+ * @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
+ */
+ public function set_cache_level( $mode ) {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ try {
+ $cf_return = $this->api->change_cache_level( $mode );
+ return $mode;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_cache_level', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Set the Cloudflare Development mode.
+ *
+ * @since 1.0
+ *
+ * @param string $mode Value for Cloudflare development mode.
+ *
+ * @return mixed Object|String Mode value if the update is successful, WP_Error otherwise.
+ */
+ public function set_devmode( $mode ) {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ if ( 0 === (int) $mode ) {
+ $value = 'off';
+ } else {
+ $value = 'on';
+ }
+
+ try {
+ $cf_return = $this->api->change_development_mode( $value );
+
+ if ( 'on' === $value ) {
+ wp_schedule_single_event( time() + 3 * HOUR_IN_SECONDS, 'rocket_cron_deactivate_cloudflare_devmode' );
+ }
+
+ return $value;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_dev_mode', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Get all the current Cloudflare settings for a given domain.
+ *
+ * @since 1.0
+ *
+ * @return mixed bool|Array Array of Cloudflare settings, false if any error connection to Cloudflare.
+ */
+ public function get_settings() {
+ if ( is_wp_error( $this->cloudflare_api_error ) ) {
+ return $this->cloudflare_api_error;
+ }
+
+ try {
+ $cf_settings = $this->api->get_settings();
+
+ foreach ( $cf_settings->result as $cloudflare_option ) {
+ switch ( $cloudflare_option->id ) {
+ case 'browser_cache_ttl':
+ $browser_cache_ttl = $cloudflare_option->value;
+ break;
+ case 'cache_level':
+ $cache_level = $cloudflare_option->value;
+ break;
+ case 'rocket_loader':
+ $rocket_loader = $cloudflare_option->value;
+ break;
+ case 'minify':
+ $cf_minify = $cloudflare_option->value;
+ break;
+ }
+ }
+ $cf_minify_value = 'on';
+
+ if ( 'off' === $cf_minify->js || 'off' === $cf_minify->css || 'off' === $cf_minify->html ) {
+ $cf_minify_value = 'off';
+ }
+
+ $cf_settings_array = [
+ 'cache_level' => $cache_level,
+ 'minify' => $cf_minify_value,
+ 'rocket_loader' => $rocket_loader,
+ 'browser_cache_ttl' => $browser_cache_ttl,
+ ];
+
+ return $cf_settings_array;
+ } catch ( Exception $e ) {
+ return new WP_Error( 'cloudflare_current_settings', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Get Cloudflare IPs. No API validation needed, all exceptions returns the default CF IPs array.
+ *
+ * @since 1.0
+ *
+ * @return Object Result of API request if successful, default CF IPs otherwise.
+ */
+ public function get_cloudflare_ips() {
+ $cf_ips = get_transient( 'rocket_cloudflare_ips' );
+ if ( false !== $cf_ips ) {
+ return $cf_ips;
+ }
+
+ try {
+ $cf_ips = $this->api->get_ips();
+
+ if ( empty( $cf_ips->success ) ) {
+ // Set default IPs from Cloudflare if call to Cloudflare /ips API does not contain a success.
+ // Prevents from making API calls on each page load.
+ $cf_ips = $this->get_default_ips();
+ }
+ } catch ( Exception $e ) {
+ // Set default IPs from Cloudflare if call to Cloudflare /ips API fails.
+ // Prevents from making API calls on each page load.
+ $cf_ips = $this->get_default_ips();
+ }
+
+ set_transient( 'rocket_cloudflare_ips', $cf_ips, 2 * WEEK_IN_SECONDS );
+
+ return $cf_ips;
+ }
+
+ /**
+ * Get default Cloudflare IPs.
+ *
+ * @since 1.0
+ *
+ * @return stdClass Default Cloudflare connecting IPs.
+ */
+ private function get_default_ips() {
+ $cf_ips = (object) [
+ 'result' => (object) [],
+ 'success' => true,
+ 'errors' => [],
+ 'messages' => [],
+ ];
+
+ $cf_ips->result->ipv4_cidrs = [
+ '173.245.48.0/20',
+ '103.21.244.0/22',
+ '103.22.200.0/22',
+ '103.31.4.0/22',
+ '141.101.64.0/18',
+ '108.162.192.0/18',
+ '190.93.240.0/20',
+ '188.114.96.0/20',
+ '197.234.240.0/22',
+ '198.41.128.0/17',
+ '162.158.0.0/15',
+ '104.16.0.0/12',
+ '172.64.0.0/13',
+ '131.0.72.0/22',
+ ];
+
+ $cf_ips->result->ipv6_cidrs = [
+ '2400:cb00::/32',
+ '2606:4700::/32',
+ '2803:f800::/32',
+ '2405:b500::/32',
+ '2405:8100::/32',
+ '2a06:98c0::/29',
+ '2c0f:f248::/32',
+ ];
+
+ return $cf_ips;
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/Subscriber.php b/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/Subscriber.php
new file mode 100644
index 00000000..f5e8f35b
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/Subscriber.php
@@ -0,0 +1,608 @@
+options = $options;
+ $this->options_api = $options_api;
+ $this->cloudflare = $cloudflare;
+ }
+
+ /**
+ * Gets the subscribed events.
+ *
+ * @since 1.0
+ *
+ * @return array subscribed events => callbacks.
+ */
+ public static function get_subscribed_events() {
+ $slug = rocket_get_constant( 'WP_ROCKET_SLUG', 'wp_rocket_settings' );
+
+ return [
+ 'rocket_varnish_ip' => 'set_varnish_localhost',
+ 'rocket_varnish_purge_request_host' => 'set_varnish_purge_request_host',
+ 'rocket_cron_deactivate_cloudflare_devmode' => 'deactivate_devmode',
+ 'after_rocket_clean_domain' => 'auto_purge',
+ 'after_rocket_clean_post' => [ 'auto_purge_by_url', 10, 3 ],
+ 'admin_post_rocket_purge_cloudflare' => 'purge_cache',
+ 'init' => [ 'set_real_ip', 1 ],
+ 'update_option_' . $slug => [ 'save_cloudflare_options', 10, 2 ],
+ 'pre_update_option_' . $slug => [ 'save_cloudflare_old_settings', 10, 2 ],
+ 'admin_notices' => [
+ [ 'maybe_display_purge_notice' ],
+ [ 'maybe_print_update_settings_notice' ],
+ ],
+ ];
+ }
+
+ /**
+ * Sets the Varnish IP to localhost if Cloudflare is active.
+ *
+ * @since 1.0
+ *
+ * @param string|array $varnish_ip Varnish IP.
+ *
+ * @return array
+ */
+ public function set_varnish_localhost( $varnish_ip ) {
+ if ( ! $this->should_filter_varnish() ) {
+ return $varnish_ip;
+ }
+
+ if ( is_string( $varnish_ip ) ) {
+ $varnish_ip = (array) $varnish_ip;
+ }
+
+ $varnish_ip[] = 'localhost';
+
+ return $varnish_ip;
+ }
+
+ /**
+ * Sets the Host header to the website domain if Cloudflare is active.
+ *
+ * @since 1.0
+ *
+ * @param string $host the host header value.
+ *
+ * @return string
+ */
+ public function set_varnish_purge_request_host( $host ) {
+ if ( ! $this->should_filter_varnish() ) {
+ return $host;
+ }
+
+ return wp_parse_url( home_url(), PHP_URL_HOST );
+ }
+
+ /**
+ * Checks if we should filter the value for the Varnish purge.
+ *
+ * @since 1.0
+ *
+ * @return bool
+ */
+ private function should_filter_varnish() {
+ // This filter is documented in inc/classes/subscriber/Addons/Varnish/VarnishSubscriber.php.
+ if ( ! apply_filters( 'do_rocket_varnish_http_purge', false ) && ! $this->options->get( 'varnish_auto_purge', 0 ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Automatically set Cloudflare development mode value to off after 3 hours to reflect Cloudflare behaviour.
+ *
+ * @since 1.0
+ */
+ public function deactivate_devmode() {
+ $this->options->set( 'cloudflare_devmode', 'off' );
+ $this->options_api->set( 'settings', $this->options->get_options() );
+ }
+
+ /**
+ * Purge Cloudflare cache automatically if Cache Everything is set as a Page Rule.
+ *
+ * @since 1.0
+ */
+ public function auto_purge() {
+ if ( ! current_user_can( 'rocket_purge_cloudflare_cache' ) ) {
+ return;
+ }
+
+ $cf_cache_everything = $this->cloudflare->has_page_rule( 'cache_everything' );
+
+ if ( is_wp_error( $cf_cache_everything ) || ! $cf_cache_everything ) {
+ return;
+ }
+
+ // Purge CloudFlare.
+ $this->cloudflare->purge_cloudflare();
+ }
+
+ /**
+ * Purge Cloudflare cache URLs automatically if Cache Everything is set as a Page Rule.
+ *
+ * @since 1.0
+ *
+ * @param WP_Post $post The post object.
+ * @param array $purge_urls URLs cache files to remove.
+ * @param string $lang The post language.
+ */
+ public function auto_purge_by_url( $post, $purge_urls, $lang ) {
+ if ( ! current_user_can( 'rocket_purge_cloudflare_cache' ) ) {
+ return;
+ }
+
+ $cf_cache_everything = $this->cloudflare->has_page_rule( 'cache_everything' );
+
+ if ( is_wp_error( $cf_cache_everything ) || ! $cf_cache_everything ) {
+ return;
+ }
+
+ // Add home URL and feeds URLs to Cloudflare clean cache URLs list.
+ $purge_urls[] = get_rocket_i18n_home_url( $lang );
+ $feed_urls = [];
+ $feed_urls[] = get_feed_link();
+ $feed_urls[] = get_feed_link( 'comments_' );
+
+ // this filter is documented in inc/functions/files.php.
+ $feed_urls = apply_filters( 'rocket_clean_home_feeds', $feed_urls );
+ $purge_urls = array_unique( array_merge( $purge_urls, $feed_urls ) );
+
+ // Purge CloudFlare.
+ $this->cloudflare->purge_by_url( $post, $purge_urls, $lang );
+ }
+
+ /**
+ * Purge CloudFlare cache.
+ *
+ * @since 1.0
+ */
+ public function purge_cache_no_die() {
+ if ( ! current_user_can( 'rocket_purge_cloudflare_cache' ) ) {
+ return;
+ }
+
+ // Purge CloudFlare.
+ $cf_purge = $this->cloudflare->purge_cloudflare();
+
+ if ( is_wp_error( $cf_purge ) ) {
+ $cf_purge_result = [
+ 'result' => 'error',
+ // translators: %s = CloudFare API return message.
+ 'message' => sprintf( __( 'WP Rocket: %s', 'rocket' ), $cf_purge->get_error_message() ),
+ ];
+ } else {
+ $cf_purge_result = [
+ 'result' => 'success',
+ 'message' => __( 'WP Rocket: Cloudflare cache successfully purged.', 'rocket' ),
+ ];
+ }
+
+ set_transient( get_current_user_id() . '_cloudflare_purge_result', $cf_purge_result );
+ }
+
+ /**
+ * Purge CloudFlare cache.
+ *
+ * @since 1.0
+ */
+ public function purge_cache() {
+ if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'rocket_purge_cloudflare' ) ) {
+ wp_nonce_ays( '' );
+ }
+
+ $this->purge_cache_no_die();
+
+ wp_safe_redirect( esc_url_raw( wp_get_referer() ) );
+ defined( 'WPMEDIA_IS_TESTING' ) ? wp_die() : exit;
+ }
+
+ /**
+ * Set Real IP from CloudFlare.
+ *
+ * @since 1.0
+ * @source cloudflare.php - https://wordpress.org/plugins/cloudflare/
+ */
+ public function set_real_ip() {
+ // only run this logic if the REMOTE_ADDR is populated, to avoid causing notices in CLI mode.
+ if ( ! isset( $_SERVER['HTTP_CF_CONNECTING_IP'], $_SERVER['REMOTE_ADDR'] ) ) {
+ return;
+ }
+
+ $cf_ips_values = $this->cloudflare->get_cloudflare_ips();
+ $cf_ip_ranges = $cf_ips_values->result->ipv6_cidrs;
+ $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
+ $ipv6 = get_rocket_ipv6_full( $ip );
+ if ( false === strpos( $ip, ':' ) ) {
+ // IPV4: Update the REMOTE_ADDR value if the current REMOTE_ADDR value is in the specified range.
+ $cf_ip_ranges = $cf_ips_values->result->ipv4_cidrs;
+ }
+
+ foreach ( $cf_ip_ranges as $range ) {
+ if (
+ ( strpos( $ip, ':' ) && rocket_ipv6_in_range( $ipv6, $range ) )
+ ||
+ ( false === strpos( $ip, ':' ) && rocket_ipv4_in_range( $ip, $range ) )
+ ) {
+ $_SERVER['REMOTE_ADDR'] = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
+ break;
+ }
+ }
+ }
+
+ /**
+ * This notice is displayed after purging the CloudFlare cache.
+ *
+ * @since 1.0
+ */
+ public function maybe_display_purge_notice() {
+ if ( ! current_user_can( 'rocket_purge_cloudflare_cache' ) ) {
+ return;
+ }
+
+ $user_id = get_current_user_id();
+ $notice = get_transient( $user_id . '_cloudflare_purge_result' );
+ if ( ! $notice ) {
+ return;
+ }
+
+ delete_transient( $user_id . '_cloudflare_purge_result' );
+
+ rocket_notice_html(
+ [
+ 'status' => $notice['result'],
+ 'message' => $notice['message'],
+ ]
+ );
+ }
+
+ /**
+ * This notice is displayed after modifying the CloudFlare settings.
+ *
+ * @since 1.0
+ */
+ public function maybe_print_update_settings_notice() {
+ $screen = get_current_screen();
+
+ if ( ! current_user_can( 'rocket_manage_options' ) || 'settings_page_wprocket' !== $screen->id ) {
+ return;
+ }
+
+ $user_id = get_current_user_id();
+ $notices = get_transient( $user_id . '_cloudflare_update_settings' );
+ if ( ! $notices ) {
+ return;
+ }
+
+ $errors = '';
+ $success = '';
+ delete_transient( $user_id . '_cloudflare_update_settings' );
+ foreach ( $notices as $notice ) {
+ if ( 'error' === $notice['result'] ) {
+ $errors .= $notice['message'] . ' ';
+ } elseif ( 'success' === $notice['result'] ) {
+ $success .= $notice['message'] . ' ';
+ }
+ }
+
+ if ( ! empty( $success ) ) {
+ rocket_notice_html(
+ [
+ 'message' => $success,
+ ]
+ );
+ }
+
+ if ( ! empty( $errors ) ) {
+ rocket_notice_html(
+ [
+ 'status' => 'error',
+ 'message' => $errors,
+ ]
+ );
+ }
+
+ }
+
+ /**
+ * Save Cloudflare dev mode admin option.
+ *
+ * @since 3.5.2
+ * @author Soponar Cristina
+ *
+ * @param string $devmode New value for Cloudflare dev mode.
+ */
+ private function save_cloudflare_devmode( $devmode ) {
+ $cloudflare_dev_mode_return = $this->cloudflare->set_devmode( $devmode );
+ if ( is_wp_error( $cloudflare_dev_mode_return ) ) {
+ return [
+ 'result' => 'error',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare development mode error: %s', 'rocket' ), $cloudflare_dev_mode_return->get_error_message() ),
+ ];
+ }
+ return [
+ 'result' => 'success',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare development mode %s', 'rocket' ), $cloudflare_dev_mode_return ),
+ ];
+ }
+
+ /**
+ * Save Cloudflare cache_level admin option.
+ *
+ * @since 3.5.2
+ * @author Soponar Cristina
+ *
+ * @param string $cache_level New value for Cloudflare cache_level.
+ */
+ private function save_cache_level( $cache_level ) {
+ // Set Cache Level to Aggressive.
+ $cf_cache_level_return = $this->cloudflare->set_cache_level( $cache_level );
+
+ if ( is_wp_error( $cf_cache_level_return ) ) {
+ return [
+ 'result' => 'error',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare cache level error: %s', 'rocket' ), $cf_cache_level_return->get_error_message() ),
+ ];
+ }
+
+ if ( 'aggressive' === $cf_cache_level_return ) {
+ $cf_cache_level_return = _x( 'Standard', 'Cloudflare caching level', 'rocket' );
+ }
+
+ return [
+ 'result' => 'success',
+ // translators: %s is the caching level returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare cache level set to %s', 'rocket' ), $cf_cache_level_return ),
+ ];
+ }
+
+ /**
+ * Save Cloudflare minify admin option.
+ *
+ * @since 3.5.2
+ * @author Soponar Cristina
+ *
+ * @param string $minify New value for Cloudflare minify.
+ */
+ private function save_minify( $minify ) {
+ $cf_minify_return = $this->cloudflare->set_minify( $minify );
+
+ if ( is_wp_error( $cf_minify_return ) ) {
+ return [
+ 'result' => 'error',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare minification error: %s', 'rocket' ), $cf_minify_return->get_error_message() ),
+ ];
+ }
+ return [
+ 'result' => 'success',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare minification %s', 'rocket' ), $cf_minify_return ),
+ ];
+ }
+
+ /**
+ * Save Cloudflare rocket loader admin option.
+ *
+ * @since 3.5.2
+ * @author Soponar Cristina
+ *
+ * @param string $rocket_loader New value for Cloudflare rocket loader.
+ */
+ private function save_rocket_loader( $rocket_loader ) {
+ $cf_rocket_loader_return = $this->cloudflare->set_rocket_loader( $rocket_loader );
+
+ if ( is_wp_error( $cf_rocket_loader_return ) ) {
+ return [
+ 'result' => 'error',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare rocket loader error: %s', 'rocket' ), $cf_rocket_loader_return->get_error_message() ),
+ ];
+ }
+ return [
+ 'result' => 'success',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare rocket loader %s', 'rocket' ), $cf_rocket_loader_return ),
+ ];
+ }
+
+ /**
+ * Save Cloudflare browser cache ttl admin option.
+ *
+ * @since 3.5.2
+ * @author Soponar Cristina
+ *
+ * @param int $browser_cache_ttl New value for Cloudflare browser cache ttl.
+ */
+ private function save_browser_cache_ttl( $browser_cache_ttl ) {
+ $cf_browser_cache_return = $this->cloudflare->set_browser_cache_ttl( $browser_cache_ttl );
+
+ if ( is_wp_error( $cf_browser_cache_return ) ) {
+ return [
+ 'result' => 'error',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare browser cache error: %s', 'rocket' ), $cf_browser_cache_return->get_error_message() ),
+ ];
+ }
+ return [
+ 'result' => 'success',
+ // translators: %s is the message returned by the CloudFlare API.
+ 'message' => '' . __( 'WP Rocket: ', 'rocket' ) . ' ' . sprintf( __( 'Cloudflare browser cache set to %s seconds', 'rocket' ), $cf_browser_cache_return ),
+ ];
+ }
+
+ /**
+ * Save Cloudflare auto settings admin option.
+ *
+ * @since 3.5.2
+ * @author Soponar Cristina
+ *
+ * @param array $auto_settings New value for Cloudflare auto_settings.
+ * @param array $old_settings Cloudflare cloudflare_old_settings.
+ */
+ private function save_cloudflare_auto_settings( $auto_settings, $old_settings ) {
+ $cf_old_settings = explode( ',', $old_settings );
+ $cloudflare_update_result = [];
+
+ // Set Cache Level to Aggressive.
+ $cf_cache_level = isset( $cf_old_settings[0] ) && 0 === $auto_settings ? 'basic' : 'aggressive';
+ $cloudflare_update_result[] = $this->save_cache_level( $cf_cache_level );
+
+ // Active Minification for HTML, CSS & JS.
+ $cf_minify = isset( $cf_old_settings[1] ) && 0 === $auto_settings ? $cf_old_settings[1] : 'on';
+ $cloudflare_update_result[] = $this->save_minify( $cf_minify );
+
+ // Deactivate Rocket Loader to prevent conflicts.
+ $cf_rocket_loader = isset( $cf_old_settings[2] ) && 0 === $auto_settings ? $cf_old_settings[2] : 'off';
+ $cloudflare_update_result[] = $this->save_rocket_loader( $cf_rocket_loader );
+
+ // Set Browser cache to 1 year.
+ $cf_browser_cache_ttl = isset( $cf_old_settings[3] ) && 0 === $auto_settings ? $cf_old_settings[3] : '31536000';
+ $cloudflare_update_result[] = $this->save_browser_cache_ttl( $cf_browser_cache_ttl );
+
+ return $cloudflare_update_result;
+ }
+
+ /**
+ * Save Cloudflare admin options.
+ *
+ * @since 1.0
+ *
+ * @param array $old_value An array of previous values for the settings.
+ * @param array $value An array of submitted values for the settings.
+ */
+ public function save_cloudflare_options( $old_value, $value ) {
+ if ( ! current_user_can( 'rocket_manage_options' ) ) {
+ return;
+ }
+
+ $is_api_keys_valid_cloudflare = get_transient( 'rocket_cloudflare_is_api_keys_valid' );
+ $submit_cloudflare_view = false;
+ if (
+ ( isset( $old_value['cloudflare_email'], $value['cloudflare_email'] ) && $old_value['cloudflare_email'] !== $value['cloudflare_email'] )
+ ||
+ ( isset( $old_value['cloudflare_api_key'], $value['cloudflare_api_key'] ) && $old_value['cloudflare_api_key'] !== $value['cloudflare_api_key'] )
+ ||
+ ( isset( $old_value['cloudflare_zone_id'], $value['cloudflare_zone_id'] ) && $old_value['cloudflare_zone_id'] !== $value['cloudflare_zone_id'] )
+ ) {
+ delete_transient( 'rocket_cloudflare_is_api_keys_valid' );
+ $is_api_keys_valid_cloudflare = $this->cloudflare->is_api_keys_valid( $value['cloudflare_email'], $value['cloudflare_api_key'], $value['cloudflare_zone_id'], true );
+ set_transient( 'rocket_cloudflare_is_api_keys_valid', $is_api_keys_valid_cloudflare, 2 * WEEK_IN_SECONDS );
+ $submit_cloudflare_view = true;
+ }
+
+ if ( ( isset( $old_value['cloudflare_devmode'], $value['cloudflare_devmode'] ) && (int) $old_value['cloudflare_devmode'] !== (int) $value['cloudflare_devmode'] ) ||
+ ( isset( $old_value['cloudflare_auto_settings'], $value['cloudflare_auto_settings'] ) && (int) $old_value['cloudflare_auto_settings'] !== (int) $value['cloudflare_auto_settings'] ) ) {
+ $submit_cloudflare_view = true;
+ }
+
+ // Revalidate Cloudflare credentials if transient is false.
+ if ( false === $is_api_keys_valid_cloudflare ) {
+ if ( isset( $value['cloudflare_email'], $value['cloudflare_api_key'], $value['cloudflare_zone_id'] ) ) {
+ $is_api_keys_valid_cloudflare = $this->cloudflare->is_api_keys_valid( $value['cloudflare_email'], $value['cloudflare_api_key'], $value['cloudflare_zone_id'] );
+ } else {
+ $is_api_keys_valid_cloudflare = false;
+ }
+ set_transient( 'rocket_cloudflare_is_api_keys_valid', $is_api_keys_valid_cloudflare, 2 * WEEK_IN_SECONDS );
+ }
+
+ // If is submit CF view & CF Credentials are invalid, display error and bail out.
+ if ( is_wp_error( $is_api_keys_valid_cloudflare ) && $submit_cloudflare_view ) {
+ $cloudflare_error_message = $is_api_keys_valid_cloudflare->get_error_message();
+ add_settings_error( 'general', 'cloudflare_api_key_invalid', __( 'WP Rocket: ', 'rocket' ) . '' . $cloudflare_error_message . '', 'error' );
+ set_transient( get_current_user_id() . '_cloudflare_update_settings', [] );
+ return;
+ }
+
+ // Update CloudFlare Development Mode.
+ $cloudflare_update_result = [];
+ if ( isset( $old_value['cloudflare_devmode'], $value['cloudflare_devmode'] ) && (int) $old_value['cloudflare_devmode'] !== (int) $value['cloudflare_devmode'] ) {
+ $cloudflare_update_result[] = $this->save_cloudflare_devmode( $value['cloudflare_devmode'] );
+ }
+
+ // Update CloudFlare settings.
+ if ( isset( $old_value['cloudflare_auto_settings'], $value['cloudflare_auto_settings'] ) && (int) $old_value['cloudflare_auto_settings'] !== (int) $value['cloudflare_auto_settings'] ) {
+ $cloudflare_update_result = array_merge( $cloudflare_update_result, $this->save_cloudflare_auto_settings( $value['cloudflare_auto_settings'], $value['cloudflare_old_settings'] ) );
+ }
+
+ set_transient( get_current_user_id() . '_cloudflare_update_settings', $cloudflare_update_result );
+ }
+
+ /**
+ * Save Cloudflare old settings when the auto settings option is enabled.
+ *
+ * @since 1.0
+ *
+ * @param array $value An array of previous values for the settings.
+ * @param array $old_value An array of submitted values for the settings.
+ *
+ * @return array settings with old settings.
+ */
+ public function save_cloudflare_old_settings( $value, $old_value ) {
+ if ( ! current_user_can( 'rocket_manage_options' ) ) {
+ return $value;
+ }
+
+ // Save old CloudFlare settings.
+ if (
+ isset( $value['cloudflare_auto_settings'], $old_value ['cloudflare_auto_settings'] )
+ &&
+ $value['cloudflare_auto_settings'] !== $old_value ['cloudflare_auto_settings']
+ &&
+ 1 === $value['cloudflare_auto_settings']
+ ) {
+ $cf_settings = $this->cloudflare->get_settings();
+ $value['cloudflare_old_settings'] = ! is_wp_error( $cf_settings )
+ ? implode( ',', array_filter( $cf_settings ) )
+ : '';
+ }
+
+ return $value;
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/UnauthorizedException.php b/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/UnauthorizedException.php
new file mode 100644
index 00000000..a06570e1
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/Addon/Cloudflare/UnauthorizedException.php
@@ -0,0 +1,7 @@
+busting_factory = $busting_factory;
+ $this->options = $options;
+ }
+
+ /**
+ * Return an array of events that this subscriber wants to listen to.
+ *
+ * @since 3.2
+ *
+ * @return array
+ */
+ public static function get_subscribed_events() {
+ $events = [
+ 'cron_schedules' => 'add_schedule',
+ 'init' => 'schedule_cache_update',
+ self::CRON_NAME => 'update_cache',
+ 'rocket_purge_cache' => 'delete_cache',
+ 'rocket_buffer' => 'cache_busting_facebook_tracking',
+ ];
+
+ return $events;
+ }
+
+ /**
+ * Add weekly interval to cron schedules.
+ *
+ * @since 3.2
+ *
+ * @param array $schedules An array of intervals used by cron jobs.
+ * @return array
+ */
+ public function add_schedule( $schedules ) {
+ if ( ! $this->is_busting_active() ) {
+ return $schedules;
+ }
+
+ $schedules['weekly'] = [
+ 'interval' => 604800,
+ 'display' => __( 'weekly', 'rocket' ),
+ ];
+
+ return $schedules;
+ }
+
+ /**
+ * (Un)Schedule the auto-update of the cache busting files.
+ *
+ * @since 3.2
+ */
+ public function schedule_cache_update() {
+ $scheduled = wp_next_scheduled( self::CRON_NAME );
+
+ if ( ! $this->is_busting_active() ) {
+ if ( $scheduled ) {
+ wp_clear_scheduled_hook( self::CRON_NAME );
+ }
+ return;
+ }
+
+ if ( ! $scheduled ) {
+ wp_schedule_event( time(), 'weekly', self::CRON_NAME );
+ }
+ }
+
+ /**
+ * Update the Facebook Pixel cache busting files.
+ *
+ * @since 3.2
+ *
+ * @return bool
+ */
+ public function update_cache() {
+ if ( ! $this->is_busting_active() ) {
+ return false;
+ }
+
+ $html = $this->busting_factory->type( 'fbsdk' )->refresh();
+
+ return $this->busting_factory->type( 'fbpix' )->refresh_all();
+ }
+
+ /**
+ * Delete Facebook Pixel cache busting files.
+ *
+ * @since 3.2
+ * @since 3.6 Argument replacement.
+ *
+ * @param string $type Type of cache clearance: 'all', 'post', 'term', 'user', 'url'.
+ * @return bool
+ */
+ public function delete_cache( $type ) {
+ if ( 'all' !== $type || ! $this->is_busting_active() ) {
+ return false;
+ }
+
+ $html = $this->busting_factory->type( 'fbsdk' )->delete();
+
+ return $this->busting_factory->type( 'fbpix' )->delete_all();
+ }
+
+ /**
+ * Process the cache busting on the HTML contents.
+ *
+ * @since 3.2
+ *
+ * @param string $html HTML contents.
+ * @return string
+ */
+ public function cache_busting_facebook_tracking( $html ) {
+ if ( ! $this->is_allowed() ) {
+ return $html;
+ }
+
+ $html = $this->busting_factory->type( 'fbsdk' )->replace_url( $html );
+
+ return $this->busting_factory->type( 'fbpix' )->replace_url( $html );
+ }
+
+ /**
+ * Tell if the cache busting should happen.
+ *
+ * @since 3.2
+ *
+ * @return bool
+ */
+ private function is_allowed() {
+ if ( defined( 'DONOTROCKETOPTIMIZE' ) && DONOTROCKETOPTIMIZE ) {
+ return false;
+ }
+
+ return $this->is_busting_active();
+ }
+
+ /**
+ * Tell if the cache busting option is active.
+ *
+ * @since 3.2
+ *
+ * @return bool
+ */
+ private function is_busting_active() {
+ return (bool) $this->options->get( 'facebook_pixel_cache', 0 );
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/Addon/GoogleTracking/GoogleAnalytics.php b/wp-content/plugins/wp-rocket/inc/Addon/GoogleTracking/GoogleAnalytics.php
new file mode 100644
index 00000000..65d95987
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/Addon/GoogleTracking/GoogleAnalytics.php
@@ -0,0 +1,245 @@
+busting_path = $busting_path . 'google-tracking/';
+ $this->busting_url = $busting_url . 'google-tracking/';
+ $this->filesystem = rocket_direct_filesystem();
+ }
+
+ /** ----------------------------------------------------------------------------------------- */
+ /** PUBLIC METHODS ========================================================================== */
+ /** ----------------------------------------------------------------------------------------- */
+
+ /**
+ * Performs the replacement process.
+ *
+ * @since 3.1
+ * @access public
+ * @author Remy Perona
+ *
+ * @param string $html HTML content.
+ * @return string
+ */
+ public function replace_url( $html ) {
+ $this->is_replaced = false;
+
+ $tag = $this->find( '';
+ }
+
+ /**
+ * Filters the script tag for the lazyload script
+ *
+ * @since 2.2.6
+ *
+ * @param $script_tag HTML tag for the lazyload script.
+ */
+ $script .= apply_filters( 'rocket_lazyload_script_tag', '' );
+
+ return $script;
+ }
+
+ /**
+ * Inserts in the HTML the script to replace the Youtube thumbnail by the iframe.
+ *
+ * @param array $args Array of arguments to populate the script options.
+ * @return void
+ */
+ public function insertYoutubeThumbnailScript( $args = [] ) {
+ echo $this->getYoutubeThumbnailScript( $args );
+ }
+
+ /**
+ * Returns the Youtube Thumbnail inline script
+ *
+ * @param array $args Array of arguments to populate the script options.
+ * @return string
+ */
+ public function getYoutubeThumbnailScript( $args = [] ) {
+ $defaults = [
+ 'resolution' => 'hqdefault',
+ 'lazy_image' => false,
+ ];
+
+ $allowed_resolutions = [
+ 'default' => [
+ 'width' => 120,
+ 'height' => 90,
+ ],
+ 'mqdefault' => [
+ 'width' => 320,
+ 'height' => 180,
+ ],
+ 'hqdefault' => [
+ 'width' => 480,
+ 'height' => 360,
+ ],
+ 'sddefault' => [
+ 'width' => 640,
+ 'height' => 480,
+ ],
+
+ 'maxresdefault' => [
+ 'width' => 1280,
+ 'height' => 720,
+ ],
+ ];
+
+ $args['resolution'] = ( isset( $args['resolution'] ) && isset( $allowed_resolutions[ $args['resolution'] ] ) ) ? $args['resolution'] : 'hqdefault';
+
+ $args = wp_parse_args( $args, $defaults );
+
+ $image = ' ';
+
+ if ( isset( $args['lazy_image'] ) && $args['lazy_image'] ) {
+ $image = ' ';
+ }
+
+ return "";
+ }
+
+ /**
+ * Inserts the CSS to style the Youtube thumbnail container
+ *
+ * @param array $args Array of arguments to populate the CSS.
+ * @return void
+ */
+ public function insertYoutubeThumbnailCSS( $args = [] ) {
+ wp_register_style( 'rocket-lazyload', false );
+ wp_enqueue_style( 'rocket-lazyload' );
+ wp_add_inline_style( 'rocket-lazyload', $this->getYoutubeThumbnailCSS( $args ) );
+ }
+
+ /**
+ * Returns the CSS for the Youtube Thumbnail
+ *
+ * @param array $args Array of arguments to populate the CSS.
+ * @return string
+ */
+ public function getYoutubeThumbnailCSS( $args = [] ) {
+ $defaults = [
+ 'base_url' => '',
+ 'responsive_embeds' => true,
+ ];
+
+ $args = wp_parse_args( $args, $defaults );
+
+ $css = '.rll-youtube-player{position:relative;padding-bottom:56.23%;height:0;overflow:hidden;max-width:100%;}.rll-youtube-player iframe{position:absolute;top:0;left:0;width:100%;height:100%;z-index:100;background:0 0}.rll-youtube-player img{bottom:0;display:block;left:0;margin:auto;max-width:100%;width:100%;position:absolute;right:0;top:0;border:none;height:auto;cursor:pointer;-webkit-transition:.4s all;-moz-transition:.4s all;transition:.4s all}.rll-youtube-player img:hover{-webkit-filter:brightness(75%)}.rll-youtube-player .play{height:72px;width:72px;left:50%;top:50%;margin-left:-36px;margin-top:-36px;position:absolute;background:url(' . $args['base_url'] . 'img/youtube.png) no-repeat;cursor:pointer}';
+
+ if ( $args['responsive_embeds'] ) {
+ $css .= '.wp-has-aspect-ratio .rll-youtube-player{position:absolute;padding-bottom:0;width:100%;height:100%;top:0;bottom:0;left:0;right:0}';
+ }
+
+ return $css;
+ }
+
+ /**
+ * Inserts the CSS needed when Javascript is not enabled to keep the display correct
+ */
+ public function insertNoJSCSS() {
+ echo $this->getNoJSCSS();
+ }
+
+ /**
+ * Returns the CSS to correctly display images when JavaScript is disabled
+ *
+ * @return string
+ */
+ public function getNoJSCSS() {
+ return ' ';
+ }
+}
diff --git a/wp-content/plugins/wp-rocket/inc/Dependencies/RocketLazyload/Iframe.php b/wp-content/plugins/wp-rocket/inc/Dependencies/RocketLazyload/Iframe.php
new file mode 100644
index 00000000..25957e81
--- /dev/null
+++ b/wp-content/plugins/wp-rocket/inc/Dependencies/RocketLazyload/Iframe.php
@@ -0,0 +1,232 @@
+ false,
+ ];
+
+ $args = wp_parse_args( $args, $defaults );
+
+ if ( ! preg_match_all( '@@iUs', $buffer, $iframes, PREG_SET_ORDER ) ) {
+ return $html;
+ }
+
+ $iframes = array_unique( $iframes, SORT_REGULAR );
+
+ foreach ( $iframes as $iframe ) {
+ if ( $this->isIframeExcluded( $iframe ) ) {
+ continue;
+ }
+
+ // Given the previous regex pattern, $iframe['atts'] starts with a whitespace character.
+ if ( ! preg_match( '@\ssrc\s*=\s*(\'|")(?.*)\1@iUs', $iframe['atts'], $atts ) ) {
+ continue;
+ }
+
+ $iframe['src'] = trim( $atts['src'] );
+
+ if ( '' === $iframe['src'] ) {
+ continue;
+ }
+
+ if ( $args['youtube'] ) {
+ $iframe_lazyload = $this->replaceYoutubeThumbnail( $iframe );
+ }
+
+ if ( empty( $iframe_lazyload ) ) {
+ $iframe_lazyload = $this->replaceIframe( $iframe );
+ }
+
+ $html = str_replace( $iframe[0], $iframe_lazyload, $html );
+
+ unset( $iframe_lazyload );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Checks if the provided iframe is excluded from lazyload
+ *
+ * @param array $iframe Array of matched patterns.
+ * @return boolean
+ */
+ public function isIframeExcluded( $iframe ) {
+
+ foreach ( $this->getExcludedPatterns() as $excluded_pattern ) {
+ if ( strpos( $iframe[0], $excluded_pattern ) !== false ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets patterns excluded from lazyload for iframes
+ *
+ * @since 2.1.1
+ *
+ * @return array
+ */
+ private function getExcludedPatterns() {
+ /**
+ * Filters the patterns excluded from lazyload for iframes
+ *
+ * @since 2.1.1
+ *
+ * @param array $excluded_patterns Array of excluded patterns.
+ */
+ return apply_filters(
+ 'rocket_lazyload_iframe_excluded_patterns',
+ [
+ 'gform_ajax_frame',
+ 'data-no-lazy=',
+ 'recaptcha/api/fallback',
+ 'loading="eager"',
+ 'data-skip-lazy',
+ 'skip-lazy',
+ ]
+ );
+ }
+
+ /**
+ * Applies lazyload on the iframe provided
+ *
+ * @param array $iframe Array of matched elements.
+ * @return string
+ */
+ private function replaceIframe( $iframe ) {
+ /**
+ * Filter the LazyLoad placeholder on src attribute
+ *
+ * @since 1.0
+ *
+ * @param string $placeholder placeholder that will be printed.
+ */
+ $placeholder = apply_filters( 'rocket_lazyload_placeholder', 'about:blank' );
+
+ $placeholder_atts = str_replace( $iframe['src'], $placeholder, $iframe['atts'] );
+ $iframe_lazyload = str_replace( $iframe['atts'], $placeholder_atts . ' data-rocket-lazyload="fitvidscompatible" data-lazy-src="' . esc_url( $iframe['src'] ) . '"', $iframe[0] );
+
+ if ( ! preg_match( '@\sloading\s*=\s*(\'|")(?:lazy|auto)\1@i', $iframe_lazyload ) ) {
+ $iframe_lazyload = str_replace( '