Merge branch 'workflow-managment' into 'master'

Workflow management (Gravity Flow)

See merge request saburly/wiaas/experiments!3
This commit was merged in pull request #3.
This commit is contained in:
Nedim Uka
2018-06-28 12:05:33 +00:00
1106 changed files with 317603 additions and 4720 deletions

File diff suppressed because one or more lines are too long

3
backend/wordpress/wp-config.php Normal file → Executable file
View File

@@ -77,7 +77,8 @@ $table_prefix = 'wp_';
*
* @link https://codex.wordpress.org/Debugging_in_WordPress
*/
define('WP_DEBUG', false);
define('WP_DEBUG', true);
define( 'SCRIPT_DEBUG', true );
/* That's all, stop editing! Happy blogging. */

View File

@@ -0,0 +1,23 @@
version: 2
jobs:
build:
machine: true
parallelism: 4
working_directory: ~/gravityflow
steps:
- checkout
- run:
name: Split the tests
command: |
mkdir ~/gravityflow/tests/acceptance-tests/acceptance/ci-split-tests
TESTFILES=$(circleci tests glob ~/gravityflow/tests/acceptance-tests/acceptance/[^_]*.php | circleci tests split --split-by=timings)
echo ${TESTFILES} | xargs -n 1 echo
cp ${TESTFILES} ~/gravityflow/tests/acceptance-tests/acceptance/ci-split-tests
- run:
name: Run acceptance tests
command: |
docker-compose run --rm codeception run --html --xml -vvv -o "groups: ci-split-tests: [tests/acceptance-tests/acceptance/ci-split-tests]" -g ci-split-tests
- store_artifacts:
path: ~/gravityflow/tests/acceptance-tests/_output
- store_test_results:
path: ~/gravityflow/tests/acceptance-tests/_output

View File

@@ -0,0 +1,18 @@
version: "2"
plugins:
csslint:
enabled: true
phpcodesniffer:
enabled: true
config:
standard: "WordPress-Core,WordPress-Docs"
checks:
WordPress Files FileName InvalidClassFileName:
enabled: false
WordPress PHP YodaConditions NotYoda:
enabled: false
exclude_patterns:
- "tests/"
- "languages/"
- "**/index.php"
- "Gruntfile.js"

View File

@@ -0,0 +1,7 @@
# Choose the method to use to download Gravity Forms.
# Only specify your GitHub token if you have access to the Gravity Forms repository.
# GITHUB_TOKEN=your-token
# Only specify your Gravity Forms License key if you don't have access to the Gravity Forms repository.
GF_KEY=your-gf-license-key

View File

@@ -0,0 +1,2 @@
# Declare files that will always have LF line endings on checkout.
*.sh text eol=lf

View File

@@ -0,0 +1,23 @@
vendor/
composer.json
composer.lock
apigen.neon
config.json
config-sample.json
js/*.min.js
css/*.min.css
node_modules
Gruntfile.js
package.json
tests/wp-tests-config.php
.svn/
codeception.yml
tests/phpunit/build/
tests/acceptance-tests/_output
tmp/
.DS_Store
tests/_output/*
.DS_Store
.env
tests/acceptance-tests/_support/_generated/AcceptanceTesterActions.php
codeclimate-report.html

View File

@@ -0,0 +1,104 @@
sudo: false
dist: trusty
language: php
cache:
directories:
- node_modules
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
matrix:
include:
- php: 5.2
dist: precise
- php: 5.3
dist: precise
- php: 7.2
env:
matrix:
- WP_VERSION=latest WP_MULTISITE=0
- WP_VERSION=4.9 WP_MULTISITE=0
- WP_VERSION=4.8 WP_MULTISITE=0
- WP_VERSION=4.7 WP_MULTISITE=0
- WP_VERSION=4.6 WP_MULTISITE=0
- WP_VERSION=4.5 WP_MULTISITE=0
- WP_VERSION=4.4 WP_MULTISITE=0
- WP_VERSION=4.3 WP_MULTISITE=0
global:
- WP_TRAVISCI=travis:phpunit
before_script:
- |
# Remove Xdebug for a huge performance increase, but not from nightly or hhvm:
stable='^[0-9\.]+$'
if [[ "$TRAVIS_PHP_VERSION" =~ $stable ]]; then
phpenv config-rm xdebug.ini
fi
- |
# Export Composer's global bin dir to PATH, but not on PHP 5.2:
if [[ ${TRAVIS_PHP_VERSION:0:3} != "5.2" ]]; then
composer config --list --global
export PATH=`composer config --list --global | grep '\[home\]' | { read a; echo "${a#* }/vendor/bin:$PATH"; }`
fi
- |
# Install the specified version of PHPUnit depending on the PHP version:
if [[ "$WP_TRAVISCI" == "travis:phpunit" ]]; then
case "$TRAVIS_PHP_VERSION" in
7.2)
echo "Using PHPUnit 6.0"
composer global require "phpunit/phpunit=6.0.*"
;;
7.1|7.0|hhvm|nightly)
echo "Using PHPUnit 5.7"
composer global require "phpunit/phpunit=5.7.*"
;;
5.6|5.5|5.4|5.3)
echo "Using PHPUnit 4.8"
composer global require "phpunit/phpunit=4.8.*"
;;
5.2)
# Do nothing, use default PHPUnit 3.6.x
echo "Using default PHPUnit, hopefully 3.6"
;;
*)
echo "No PHPUnit version handling for PHP version $TRAVIS_PHP_VERSION"
exit 1
;;
esac
fi
- npm --version
- node --version
- nvm install 6.9.1
- npm install -g grunt-cli
- npm install
- npm prune
- mysql --version
- phpenv versions
- php --version
- |
# Debug PHP extensions, but not on HHVM because the command hangs indefinitely:
if [[ "$TRAVIS_PHP_VERSION" != 'hhvm' ]]; then
php -m
fi
- npm --version
- node --version
- which phpunit
- phpunit --version
- curl --version
- grunt --version
- git --version
- svn --version
- git clone https://${GFTOKEN}@github.com/gravityforms/gravityforms.git $PWD/tmp/gravityforms
- bash tests/bin/install.sh wordpress_unit_tests root '' localhost $WP_VERSION
script: phpunit
notifications:
slack:
on_start: never
on_failure: always
on_success: change
secure: RrZ3tDCpJ4G/VmiKVgnAKaPM9vkbrq0c2Qhyxbrg6dMBQEIBzJZJnimq0VuJWzJSjtmGgQmbU8Dg54TEVNIU7zZAi2/9M1CQLRY+dH3PWBjv55+YDtXQRXiChnnR8C47dW9QPV/9EgJ2ksy7K4gIDTuhzNdVjBdPjU4xk4c2Y5sfS7F3EaWw16hXCcIL2PsksUFAtdtaCPpC/JSPS6hjQWc2Uxr4iBeVW8aZW06F9guAF12Dsp9voVZUnUZDso/aCfoqC5QPLu1G5WbYDzL+Fd0ph7W5RHP+koMW+cuFQIj6Fl0A8CfILJWhp+REQx1rxTHJgJ77oNuefv3xwcD7U6NNp1zuKYAQaJAlPYMv+rHngB1zwYdcL/VMg5k4KUUMsG3b/mN+yuW9HYfgNlYAUriE+HKECBdphCfOx5PhG0IXIfa3yVP92sH0Yo7CCQRBwfYKatLDAsTarTBAq/yFSGbeI8ivUBLp9tpG6/mxgkngPuSMtODyCm4EPC+0CuCm//ta+xOGNxxZ8Q+Rg+X7q/vlz6ygC7IMKFa7TKlCeA9dGwro9My8Os8TVBdqwjhBiIY/kMDwCNis1M4bHd7Yds2HfCq4CRJJln6+N9mEC+jjxKmtL3dB+/yWT6Hjkk9Qa4FtDHe1Dk6E7cfBDQjdKJLpNydJPxuazbInvURYnOc=
webhooks:
urls:
secure: gEfRUPngUkfJIke6ZBxED14AHlcDLoKyX+j7UJR9hHWiI9ydoBVU7X+td8sgx0oBgckDvbXdHgZl+LR7ftV5CIAhyeWTgJPny5+YsECU3+SSnL6NvwL3SGGavhhgt495NkW0fyvW2m92TqBSgcQMnDkd7Klxb+7euxOIwEKsVtVWd0UAQgr2Pkg4T35ZHtvqPFsIOdOqUB8l93H6cTl4Z1/bHHlPKWKaGMNkGca/VEP/+NIyi2uYVqcbsGR439vvOw/tJI+pn6202K0glPF7zZDjCvzZ4litSgzHh3n6C4qOciVFqvCWmC2JSFVfii5ZhrLORsvHRmph7iuiXvsrdJ+U/RydB1kP5K0ONGf9NR79ulLT07s+FNd4tRO+YHjfKtPfxpbkRMQG7IMyay2tym2cI55nw2Zm3xkgXmU33NHNczDbQujY+qs7vKnnFX4ExyPKUWFrKQMNaD3102kIf2O5jeyNOnLxokYu4yyC0thPenCsBwLysD0VLDG4Mu/Gw9kVLp7BtR4tBqzRzwf0fcXMpkmuzQlaCExJScwyk6TwpFvW/GQ4DIKAHVFagMNme2Ka3piQG88mJJuGDqcFMiWsvyBVGPb24U0SvALB8EWcceWVyJ7QDshjE1zkUjBnSTlaOzQbAxTf/19Ipd/rQnbN02mE90HN/2CV2AHlF9U=

View File

@@ -0,0 +1,8 @@
[main]
host = https://www.transifex.com
[gravityflow.gravityflow]
file_filter = languages/gravityflow-<lang>.po
source_file = languages/gravityflow.pot
source_lang = en_US
type = PO

View File

@@ -0,0 +1,56 @@
FROM php:7.0-cli
MAINTAINER Steve Henty steve@gravityflow.io
# Install required system packages
RUN apt-get update && \
apt-get -y install \
git \
zlib1g-dev \
libssl-dev \
mysql-client \
sudo less \
--no-install-recommends && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Install php extensions
RUN docker-php-ext-install \
bcmath \
zip
# Add mysql driver required for wp-browser
RUN docker-php-ext-install mysqli
# Configure php
RUN echo "date.timezone = UTC" >> /usr/local/etc/php/php.ini
# Install composer
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN curl -sS https://getcomposer.org/installer | php -- \
--filename=composer \
--install-dir=/usr/local/bin
RUN composer global require --optimize-autoloader \
"hirak/prestissimo"
# Add WP-CLI
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
RUN chmod +x wp-cli.phar
RUN mv wp-cli.phar /usr/local/bin/wp
# Prepare application
WORKDIR /repo
# Install vendor
COPY ./composer.json /repo/composer.json
RUN composer install --prefer-dist --optimize-autoloader
# Add source-code
COPY . /repo
WORKDIR /project
ADD docker-entrypoint.sh /
RUN ["chmod", "+x", "/docker-entrypoint.sh"]

View File

@@ -0,0 +1 @@
- Fixed an issue which prevents tokens from working correctly with role assignees.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
actor: Tester
paths:
tests: tests/acceptance-tests
log: tests/acceptance-tests/_output
data: tests/acceptance-tests/_data
helpers: tests/acceptance-tests/_support
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
modules:
config:
WPLoader:
wpRootFolder: /Users/steve/Pressmatic Sites/testing/app/public
dbName: pressmatic
dbHost: 192.168.55.100:5007
dbUser: root
dbPassword: root
wpDebug: true
tablePrefix: wp_
domain: testing.dev
plugins: ['gravityforms/gravityforms.php', 'gravityflow/gravityflow.php']
activatePlugins: ['gravityforms/gravityforms.php', 'gravityflow/gravityflow.php']
WPBrowser:
url: 'http://testing.dev'
adminUsername: admin
adminPassword: password
adminUrl: /wp-admin
WPDb:
dsn: 'mysql:host=testing.dev;dbname=pressmatic'
user: root
password: root
populate: true
cleanup: true
url: 'http://testing.dev'
tablePrefix: wp_
WPWebDriver:
url: 'http://testing.dev'
browser: chrome
window_size: '1524x1768'
restart: true
wait: 1
adminUsername: admin
adminPassword: password
adminUrl: /wp-admin

View File

@@ -0,0 +1,45 @@
actor: Tester
paths:
tests: tests/acceptance-tests
log: tests/acceptance-tests/_output
data: tests/acceptance-tests/_data
helpers: tests/acceptance-tests/_support
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
modules:
config:
WPLoader:
wpRootFolder: /path/to/wordpress
dbName: acceptance-tests
dbHost: acceptance-tests.dev
dbUser: external
dbPassword: external
wpDebug: true
tablePrefix: wp_
domain: acceptance-tests.dev
plugins: ['gravityforms/gravityforms.php', 'gravityflow/gravityflow.php']
activatePlugins: ['gravityforms/gravityforms.php', 'gravityflow/gravityflow.php']
WPBrowser:
url: 'http://acceptance-tests.dev'
adminUsername: admin
adminPassword: password
adminUrl: /wp-admin
WPDb:
dsn: 'mysql:host=acceptance-tests.dev;dbname=acceptance-tests'
user: external
password: external
populate: true
cleanup: true
url: 'http://acceptance-tests.dev'
tablePrefix: wp_
WPWebDriver:
url: 'http://acceptance-tests.dev'
browser: chrome
window_size: '1524x1768'
restart: true
wait: 1
adminUsername: admin
adminPassword: password
adminUrl: /wp-admin

View File

@@ -0,0 +1,36 @@
actor: Tester
params:
- env
paths:
tests: tests/acceptance-tests
log: tests/acceptance-tests/_output
data: tests/acceptance-tests/_data
helpers: tests/acceptance-tests/_support
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
modules:
config:
WPWebDriver:
host: chrome
url: 'http://wordpress'
browser: chrome
port: 4444
window_size: '1024x768'
restart: true
wait: 0
adminUsername: admin
adminPassword: password
adminPath: /wp-admin
WPLoader:
wpRootFolder: /wp-core
dbName: wordpress
dbHost: mysql
dbUser: wordpress
dbPassword: wordpress
wpDebug: false
tablePrefix: wp_
domain: wordpress
plugins: ['gravityforms/gravityforms.php', 'gravityflow/gravityflow.php']
activatePlugins: ['gravityforms/gravityforms.php', 'gravityflow/gravityflow.php']

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

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

View File

@@ -0,0 +1,36 @@
.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;
}
.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;
}
}

View File

@@ -0,0 +1,423 @@
.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;
}
.gravityflow-note-body-wrap{
border: 1px solid #E0E0E0;
margin-left: 100px;
padding: 10px;
display:block;
}
.rtl .gravityflow-note-body-wrap{
margin-right: 100px;
}
.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;
}
.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-left-style: none;
border-right-width: 3px;
border-right-style: solid;
}
@media print {
.gravityflow-dicussion-item-hidden {
display:block !important;
}
.gravityflow-dicussion-item-toggle-display {
display: none;
}
}

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

View File

@@ -0,0 +1,256 @@
.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;
}
.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;
}
.rtl table.gforms_form_settings tr.gravityflow_sub_setting td{
padding-right: 10px;
}
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{
width:32px;
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 {
width:32px;
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;
}
.settings-field-map-table .custom-value-container:hover .custom-value-reset { display:block; }
.gravityflow-sub-setting{
margin-bottom: 10px;
}

View File

@@ -0,0 +1,848 @@
/* 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;
}
#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;
}
#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;
}
#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;
}
.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;
}
.gravityflow-actions-lock{
margin-right: 5px;
}
.rtl .gravityflow-actions-lock{
margin-left: 5px;
}
.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;
}
.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-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 .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;
}
.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;
}
.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;
}
.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;
}
#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;
}
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%;
}

View File

@@ -0,0 +1,132 @@
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-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;
}
.gravityflow-actions-lock{
margin-right: 5px;
}
.rtl .gravityflow-actions-lock{
margin-left: 5px;
}
.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;
}

View File

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

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

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

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

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

View File

@@ -0,0 +1,47 @@
version: '2'
services:
codeception:
build: .
depends_on:
- wordpress
environment:
- GITHUB_TOKEN=$GITHUB_TOKEN
- GF_KEY=$GF_KEY
volumes:
- ./:/project
- wp-core:/wp-core
- ./:/wp-core/wp-content/plugins/gravityflow
entrypoint: /docker-entrypoint.sh
wordpress:
image: wordpress:latest
depends_on:
- mysql
- chrome
volumes:
- wp-core:/var/www/html
- ./:/var/www/html/wp-content/plugins/gravityflow
ports:
- 8080:80
environment:
WORDPRESS_DB_PASSWORD: wordpress
mysql:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: wordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
chrome:
environment:
- DBUS_SESSION_BUS_ADDRESS=/dev/null
volumes:
- /dev/shm:/dev/shm
image: selenium/standalone-chrome-debug:3.7.1
ports:
- '4444'
- '5900:5900'
volumes:
wp-core:

View File

@@ -0,0 +1,60 @@
#!/bin/bash
# Allows WP CLI to run with the right permissions.
wp-su() {
sudo -E -u www-data wp "$@"
}
# Clean up from previous tests
rm -rf /wp-core/wp-content/uploads/gravity_forms
# Make sure permissions are correct.
cd /wp-core
chown www-data:www-data wp-content/plugins
chmod 755 wp-content/plugins
# Make sure the database is up and running.
while ! mysqladmin ping -hmysql --silent; do
echo 'Waiting for the database'
sleep 1
done
echo 'The database is ready'
# Make sure WordPress is installed.
if ! $(wp-su core is-installed); then
echo "Installing WordPress"
wp-su core install --url=wordpress --title=tests --admin_user=admin --admin_email=test@test.com
# The development version of Gravity Flow requires SCRIPT_DEBUG
wp-su core config --dbhost=mysql --dbname=wordpress --dbuser=root --dbpass=wordpress --extra-php="define( 'SCRIPT_DEBUG', true );" --force
fi
if [ -z ${GITHUB_TOKEN} ]; then
echo "Installing the latest version of Gravity Forms using the CLI"
wp-su plugin install gravityformscli --force --activate
wp-su gf install --key=${GF_KEY} --activate --force --quiet
echo "Gravity Forms installation complete"
wp-su gf tool verify-checksums
else
rm -rf /wp-core/wp-content/plugins/gravityforms
echo "Grabbing the latest development master of Gravity Forms"
git clone -b master --single-branch https://$GITHUB_TOKEN@github.com/gravityforms/gravityforms.git /wp-core/wp-content/plugins/gravityforms
fi
cd /project
exec "/repo/vendor/bin/codecept" "$@"

View File

@@ -0,0 +1,11 @@
<?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 xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by Fontastic.me</metadata>
<defs>
<font id="gravityflow" horiz-adv-x="512">
<font-face font-family="gravityflow" units-per-em="512" ascent="480" descent="-32"/>
<missing-glyph horiz-adv-x="512" />
<glyph glyph-name="gravityflow" unicode="&#97;" d="M173 256l149-86c26-14 51-17 79-6 33 13 56 41 59 77 2 29-10 58-31 78-25 24-59 29-91 21-6-2-17-6-24-9-4-1-6-2-8-2-13-1-20 16-8 23 25 15 59 19 81 17 28-3 54-15 73-34 24-24 36-55 35-88-1-47-28-87-70-106-27-11-56-14-84-5-9 3-15 6-24 11l-150 84-8-13-18 45 48 7z m166-12l-149 86c-27 14-51 17-79 6-33-13-56-40-59-77-2-28 9-58 31-78 25-24 58-29 91-20 6 1 16 5 24 8 3 1 6 2 8 2 13 2 20-16 8-23-25-14-59-19-82-17-27 3-53 15-73 35-23 23-35 54-34 87 1 47 27 87 70 106 26 12 56 14 83 6 10-3 16-7 25-12l150-84 8 13 18-45-48-6z"/>
</font></defs></svg>

After

Width:  |  Height:  |  Size: 983 B

View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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

View File

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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

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
/**
* 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->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->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;
}
}

View File

@@ -0,0 +1,277 @@
<?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 );
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 );
}
}

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

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

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 ? $current_step->get_id() : -1;
$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,327 @@
<?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;
}
return $this->get_display_name( rgar( $entry, $input_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 );
}
$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 ( ( ! $this->is_form_editor() && $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,275 @@
<?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 );
$display_names = $this->get_display_names( $value );
return GFCommon::implode_non_blank( ', ', $display_names );
}
/**
* 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,239 @@
<?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;
}
return $this->get_display_name( rgar( $entry, $input_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

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

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

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