Initial commit

This commit is contained in:
2020-04-07 13:03:04 +00:00
committed by Gitium
commit 00f842d9bf
1673 changed files with 471161 additions and 0 deletions

View File

@ -0,0 +1,66 @@
<?php
/**
* The abstract class for module definition.
*
* @package ThemeIsleSDK
* @subpackage Loader
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 3.0.0
*/
namespace ThemeisleSDK\Common;
use ThemeisleSDK\Product;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Abstract_Module.
*
* @package ThemeisleSDK\Common
*/
abstract class Abstract_Module {
/**
* Product which use the module.
*
* @var Product $product Product object.
*/
protected $product = null;
/**
* Can load the module for the selected product.
*
* @param Product $product Product data.
*
* @return bool Should load module?
*/
public abstract function can_load( $product );
/**
* Bootstrap the module.
*
* @param Product $product Product object.
*/
public abstract function load( $product );
/**
* Check if the product is from partner.
*
* @param Product $product Product data.
*
* @return bool Is product from partner.
*/
public function is_from_partner( $product ) {
foreach ( Module_Factory::$domains as $partner_domain ) {
if ( strpos( $product->get_store_url(), $partner_domain ) !== false ) {
return true;
}
}
return array_key_exists( $product->get_slug(), Module_Factory::$slugs );
}
}

View File

@ -0,0 +1,108 @@
<?php
/**
* The module factory class.
*
* @package ThemeIsleSDK
* @subpackage Loader
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 3.0.0
*/
namespace ThemeisleSDK\Common;
use ThemeisleSDK\Product;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Job_Factory
*
* @package ThemeisleSDK\Common
*/
class Module_Factory {
/**
* Partners slugs.
*
* @var array $SLUGS Partners product slugs.
*/
public static $slugs = [
'zermatt' => true,
'neto' => true,
'olsen' => true,
'benson' => true,
'romero' => true,
'carmack' => true,
'puzzle' => true,
'broadsheet' => true,
'girlywp' => true,
'veggie' => true,
'zeko' => true,
'maishawp' => true,
'didi' => true,
'liber' => true,
'medicpress-pt' => true,
'adrenaline-pt' => true,
'consultpress-pt' => true,
'legalpress-pt' => true,
'gympress-pt' => true,
'readable-pt' => true,
'bolts-pt' => true,
];
/**
* Partners domains.
*
* @var array $DOMAINS Partners domains.
*/
public static $domains = [
'proteusthemes.com',
'anarieldesign.com',
'prothemedesign.com',
'cssigniter.com',
];
/**
* Map which contains all the modules loaded for each product.
*
* @var array Mapping array.
*/
private static $modules_attached = [];
/**
* Load availabe modules for the selected product.
*
* @param Product $product Loaded product.
* @param array $modules List of modules.
*/
public static function attach( $product, $modules ) {
if ( ! isset( self::$modules_attached[ $product->get_slug() ] ) ) {
self::$modules_attached[ $product->get_slug() ] = [];
}
foreach ( $modules as $module ) {
$class = 'ThemeisleSDK\\Modules\\' . ucwords( $module, '_' );
/**
* Module object.
*
* @var Abstract_Module $module_object Module instance.
*/
$module_object = new $class( $product );
if ( ! $module_object->can_load( $product ) ) {
continue;
}
self::$modules_attached[ $product->get_slug() ][ $module ] = $module_object->load( $product );
}
}
/**
* Products/Modules loaded map.
*
* @return array Modules map.
*/
public static function get_modules_map() {
return self::$modules_attached;
}
}

View File

@ -0,0 +1,126 @@
<?php
/**
* The main loader class for ThemeIsle SDK
*
* @package ThemeIsleSDK
* @subpackage Loader
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK;
use ThemeisleSDK\Common\Module_Factory;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Singleton loader for ThemeIsle SDK.
*/
final class Loader {
/**
* Singleton instance.
*
* @var Loader instance The singleton instance
*/
private static $instance;
/**
* Current loader version.
*
* @var string $version The class version.
*/
private static $version = '2.0.0';
/**
* Holds registered products.
*
* @var array The products which use the SDK.
*/
private static $products = [];
/**
* Holds available modules to load.
*
* @var array The modules which SDK will be using.
*/
private static $available_modules = [
'dashboard_widget',
'rollback',
'uninstall_feedback',
'licenser',
'endpoint',
'notification',
'logger',
'translate',
'review',
'recommendation',
];
/**
* Initialize the sdk logic.
*/
public static function init() {
if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Loader ) ) {
self::$instance = new Loader();
$modules = array_merge( self::$available_modules, apply_filters( 'themeisle_sdk_modules', [] ) );
foreach ( $modules as $key => $module ) {
if ( ! class_exists( 'ThemeisleSDK\\Modules\\' . ucwords( $module, '_' ) ) ) {
unset( $modules[ $key ] );
}
}
self::$available_modules = $modules;
}
}
/**
* Register product into SDK.
*
* @param string $base_file The product base file.
*
* @return Loader The singleton object.
*/
public static function add_product( $base_file ) {
if ( ! is_file( $base_file ) ) {
return self::$instance;
}
$product = new Product( $base_file );
Module_Factory::attach( $product, self::get_modules() );
self::$products[ $product->get_slug() ] = $product;
return self::$instance;
}
/**
* Get all registered modules by the SDK.
*
* @return array Modules available.
*/
public static function get_modules() {
return self::$available_modules;
}
/**
* Get all products using the SDK.
*
* @return array Products available.
*/
public static function get_products() {
return self::$products;
}
/**
* Get the version of the SDK.
*
* @return string The version.
*/
public static function get_version() {
return self::$version;
}
}

View File

@ -0,0 +1,466 @@
<?php
/**
* The blog dashboard model class for ThemeIsle SDK
*
* @package ThemeIsleSDK
* @subpackage Modules
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Product;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Blog dashboard widget module for ThemeIsle SDK.
*/
class Dashboard_Widget extends Abstract_Module {
/**
* Fetched feeds items.
*
* @var array Feed items.
*/
private $items = array();
/**
* Dashboard widget title.
*
* @var string $dashboard_name Dashboard name.
*/
private $dashboard_name = '';
/**
* Dashboard widget feed sources.
*
* @var array $feeds Feed url.
*/
private $feeds = [];
/**
* Should we load this module.
*
* @param Product $product Product object.
*
* @return bool
*/
public function can_load( $product ) {
if ( $this->is_from_partner( $product ) ) {
return false;
}
if ( ! apply_filters( $product->get_slug() . '_load_dashboard_widget', true ) ) {
return false;
}
return true;
}
/**
* Registers the hooks.
*
* @param Product $product Product to load.
*
* @return Dashboard_Widget Module instance.
*/
public function load( $product ) {
if ( apply_filters( 'themeisle_sdk_hide_dashboard_widget', false ) ) {
return;
}
$this->product = $product;
$this->dashboard_name = apply_filters( 'themeisle_sdk_dashboard_widget_name', 'WordPress Guides/Tutorials' );
$this->feeds = apply_filters(
'themeisle_sdk_dashboard_widget_feeds',
[
'https://themeisle.com/blog/feed',
]
);
add_action( 'wp_dashboard_setup', array( &$this, 'add_widget' ) );
add_action( 'wp_network_dashboard_setup', array( &$this, 'add_widget' ) );
add_filter( 'themeisle_sdk_recommend_plugin_or_theme', array( &$this, 'recommend_plugin_or_theme' ) );
return $this;
}
/**
* Add widget to the dashboard
*
* @return string|void
*/
function add_widget() {
global $wp_meta_boxes;
if ( isset( $wp_meta_boxes['dashboard']['normal']['core']['themeisle'] ) ) {
return;
}
wp_add_dashboard_widget(
'themeisle',
$this->dashboard_name,
[
$this,
'render_dashboard_widget',
]
);
}
/**
* Render widget content
*/
function render_dashboard_widget() {
$this->setup_feeds();
if ( empty( $this->items ) || ! is_array( $this->items ) ) {
return;
}
?>
<style type="text/css">
#themeisle ul li.ti-dw-recommend-item {
padding-left: 7px;
border-top: 1px solid #eee;
margin-bottom: 0px;
padding-top: 6px;
}
#themeisle h2.hndle {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABbCAMAAADncTNAAAAAtFBMVEVHcEyAgIB/f3+xsbGgoaGBgYGCgoKKioqAgIC1tbW5ubnFx8iAgIDU1taBgYGCgoKAgIC0tLXW19jW2NiAgIC3uLiBgYHLzMy4uLhycnLW19d/f3/T1NW0tLTX19mVlZWvr6+BgYHl5eWKiottbW5JSUnW2Nm5ubnh4eHT1NWVlZVjY2N4eHh9fX6pqqq+v79PT0/39/fu7u7Nzc7Z2ttYWFgBAQHDw8P////JysoZGRk0NTZqJc/sAAAAIXRSTlMA0FL7/oEnEPL6eibivm9gwJya76/enFq2CXI+2lFAyM8GATmPAAADj0lEQVR4Xu2YaW/iOhSGAwRCWDosnXa6znjJvm8svf//f12TuARyhiR2pfnUR6gSEnr0+uT4xK7yRb755pvhHePli5K7Bfpkuhoq8ozRJdMH+WWha6Z3sqYparCSLRJqspjImVbANJU03cNMMpofAwQZCGsmpQYyFvVM0Q00OQ9koMl5IPcCoro+RA1Dt2Ea9n9eZ0+YHJLkgIlkDywQx00wCTyaReiKH8LbNU9ybJOdkchV6QFxyCFLbVvdfaREqgUWg/tx2UbqIcK2Hex2TdGLwFTjIj3XP3YfCZFsb23KRZn/3263oymSFI0/a5S4PqUBjoBIJBDjeEhCN0wxQSRybIxtJ3K5SGzuE/vAwIQc8ZmMMJFAIM4oikZItfEFtorGgoE43FObwqHU68OtPCnOz8KZ2Jbl5LgkSW0Tc7YyIz/EFWmS4jMbiZU5mJOmKRaJpKGGyLZtDJh3iyaNUu/3+xyKnrtFL71EG+FTiMpENhQtxUQ8kSOXCIr2tnCNhg/gTX0SHYFp0t7TCwQZ7U841yoHrW6rtGroUwTWVnLMssxx+H4bgZcSOFf5MYx0Ae8FghomMDyC2EBNImBywPkNTDNqGLQpIg2TjUNU8tBy9DQMo0DAZF16rAi7vJAtFTIYFAHUc6hIRW6OuOhJgaCSwmDEAYK4oa7ro+qIEyJU/US7KTJKPNSFT9tFgVFBu0SF1y7yjX4masRA9Da7EFGj28R/BkQz6xGIOurkx38T/bKs9Uk8aIiMwm/Jw0VP1yLrJwt13xAxvABBgsK4KWLov35DkRF7ZaqgzuZ7MQ8MOntmVYyAqKTwaICKqvSUFnVccMN5sziEP/5+xGDTahbH5Q3ZB76zr8fI+nJtvUUU3t3ml5GKviK/npCg3CGodnuJ4JVkfRFJYGVDBZrqKnn9RLf+CzDTS5PaN5J38+auzX4ykU4Qoj0rdKfcYs5ijfo9OL/uRUgZyQr7NCWtWwiUSLc4arfJa7lpszTA1OJZAQ8w8dXFrR5YHzCWSnS3pZ18tOi4Ps4vl/c7i/6qomjRecN+UubrPyPGn/VEMU3T0UFHkaPzpgjxmJsnjmrtionlMDZiog0TsY/DPtn8SXtlBvbtxKtwopy7lqW3smQO+yoGE1Uu55GJ3pmI8ygoejZNnqj0vnIRCyTKfLstRdtStGQi09myUsvwvlkuzSUXbV+Xz5ryBebV33fln/A/moud69FZiEYAAAAASUVORK5CYII=');
background-repeat: no-repeat;
background-position: 92% 50%;
background-size: 25px;
}
#themeisle .inside {
padding: 0;
}
.ti-feed-list {
padding: 0 12px 5px;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
}
.ti-dw-feed-item a {
display: flex;
align-items: center;
margin-bottom: 5px;
padding: 5px;
transition: .2s ease;
border-radius: 3px;
}
.ti-dw-feed-item a:hover {
background-color: #f8f8f8;
}
.ti-dw-feed-item a:hover .ti-dw-date-container {
opacity: .9;
}
.ti-dw-feed-item .ti-dw-month-container {
margin-top: -5px;
text-transform: uppercase;
font-size: 10px;
letter-spacing: 1px;
font-weight: 700;
}
.ti-dw-feed-item .ti-dw-date-container {
border-radius: 3px;
transition: .2s ease;
min-height: 35px;
margin-right: 5px;
min-width: 35px;
text-align: center;
border: 1px solid #2a6f97;
color: #fff;
background: #2ea2cc;
display: flex;
flex-direction: column;
justify-content: center;
}
.ti-dw-footer {
padding: 0 12px 5px;
text-align: center;
}
.ti-dw-recommend-item {
display: block;
}
.ti-dw-recommend-item span {
color: #72777c;
}
.ti-dw-powered-by {
font-size: 11px;
margin-top: 3px;
display: block;
color: #72777c;
}
.ti-dw-powered-by span {
font-weight: 600;
}
</style>
<?php do_action( 'themeisle_sdk_dashboard_widget_before', $this->product ); ?>
<ul class="ti-feed-list">
<?php
foreach ( $this->items as $item ) {
?>
<li class="ti-dw-feed-item">
<a href="
<?php
echo add_query_arg(
array(
'utm_source' => 'wpadmin',
'utm_campaign' => 'feed',
'utm_medium' => 'dashboard_widget',
),
$item['link']
);
?>
" target="_blank">
<span class="ti-dw-date-container"><span
class="ti-dw-day-container"><?php echo date( 'd', $item['date'] ); ?></span> <span
class="ti-dw-month-container"><?php echo substr( date( 'M', $item['date'] ), 0, 3 ); ?></span></span><?php echo $item['title']; ?>
</a>
</li>
<?php
}
?>
</ul>
<?php
$recommend = apply_filters( 'themeisle_sdk_recommend_plugin_or_theme', array() );
if ( ! is_array( $recommend ) || empty( $recommend ) ) {
return;
}
$type = $recommend['type'];
if ( ( 'theme' === $type && ! current_user_can( 'install_themes' ) ) ) {
return;
}
if ( ( 'plugin' === $type && ! current_user_can( 'install_plugins' ) ) ) {
return;
}
add_thickbox();
$url = add_query_arg(
[
'theme' => $recommend['slug'],
],
network_admin_url( 'theme-install.php' )
);
if ( 'plugin' === $type ) {
$url = add_query_arg(
array(
'tab' => 'plugin-information',
'plugin' => $recommend['slug'],
),
network_admin_url( 'plugin-install.php' )
);
}
?>
<div class="ti-dw-footer">
<span class="ti-dw-recommend-item ">
<span class="ti-dw-recommend"><?php echo apply_filters( 'themeisle_sdk_dashboard_popular_label', sprintf( 'Popular %s', ucwords( $type ) ) ); ?>
: </span>
<?php
echo trim(
str_replace(
array(
'lite',
'Lite',
'(Lite)',
'(lite)',
),
'',
$recommend['name']
)
);
?>
(<a class="thickbox open-plugin-details-modal"
href="<?php echo $url . '&TB_iframe=true&width=600&height=500'; ?>"><?php echo apply_filters( 'themeisle_sdk_dashboard_install_label', 'Install' ); ?></a>)
</span>
<span class="ti-dw-powered-by"><span><?php echo apply_filters( 'themeisle_sdk_dashboard_widget_powered_by', esc_attr( sprintf( 'Powered by %s', $this->product->get_friendly_name() ) ) ); ?></span></span>
</div>
<?php
}
/**
* Setup feed items.
*/
private function setup_feeds() {
if ( false === ( $items_normalized = get_transient( 'themeisle_sdk_feed_items' ) ) ) {
// Load SimplePie Instance.
$feed = fetch_feed( $this->feeds );
// TODO report error when is an error loading the feed.
if ( is_wp_error( $feed ) ) {
return;
}
$items = $feed->get_items( 0, 5 );
foreach ( (array) $items as $item ) {
$items_normalized[] = array(
'title' => $item->get_title(),
'date' => $item->get_date( 'U' ),
'link' => $item->get_permalink(),
);
}
set_transient( 'themeisle_sdk_feed_items', $items_normalized, 48 * HOUR_IN_SECONDS );
}
$this->items = $items_normalized;
}
/**
* Either the current product is installed or not.
*
* @param array $val The current recommended product.
*
* @return bool Either we should exclude the plugin or not.
*/
public function remove_current_products( $val ) {
if ( 'theme' === $val['type'] ) {
$exist = wp_get_theme( $val['slug'] );
return ! $exist->exists();
} else {
$all_plugins = array_keys( get_plugins() );
foreach ( $all_plugins as $slug ) {
if ( strpos( $slug, $val['slug'] ) !== false ) {
return false;
}
}
return true;
}
}
/**
* Contact the API and fetch the recommended plugins/themes
*/
function recommend_plugin_or_theme() {
$products = $this->get_product_from_api();
if ( ! is_array( $products ) ) {
$products = array();
}
$products = array_filter( $products, array( $this, 'remove_current_products' ) );
$products = array_merge( $products );
if ( count( $products ) > 1 ) {
shuffle( $products );
$products = array_slice( $products, 0, 1 );
}
$to_recommend = isset( $products[0] ) ? $products[0] : $products;
return $to_recommend;
}
/**
* Fetch products from the recomended section.
*
* @return array|mixed The list of products to use in recomended section.
*/
function get_product_from_api() {
if ( false === ( $products = get_transient( 'themeisle_sdk_products' ) ) ) {
$products = array();
$all_themes = $this->get_themes_from_wporg( 'themeisle' );
$all_plugins = $this->get_plugins_from_wporg( 'themeisle' );
static $allowed_products = [
'hestia' => true,
'neve' => true,
'visualizer' => true,
'feedzy-rss-feeds' => true,
'wp-product-review' => true,
'otter-blocks' => true,
'themeisle-companion' => true,
];
foreach ( $all_themes as $theme ) {
if ( $theme->active_installs < 4999 ) {
continue;
}
if ( ! isset( $allowed_products[ $theme->slug ] ) ) {
continue;
}
$products[] = array(
'name' => $theme->name,
'type' => 'theme',
'slug' => $theme->slug,
'installs' => $theme->active_installs,
);
}
foreach ( $all_plugins as $plugin ) {
if ( $plugin->active_installs < 4999 ) {
continue;
}
if ( ! isset( $allowed_products[ $plugin->slug ] ) ) {
continue;
}
$products[] = array(
'name' => $plugin->name,
'type' => 'plugin',
'slug' => $plugin->slug,
'installs' => $plugin->active_installs,
);
}
set_transient( 'themeisle_sdk_products', $products, 6 * HOUR_IN_SECONDS );
}
return $products;
}
/**
* Fetch themes from wporg api.
*
* @param string $author The author name.
*
* @return array The list of themes.
*/
function get_themes_from_wporg( $author ) {
$products = wp_remote_get(
'https://api.wordpress.org/themes/info/1.1/?action=query_themes&request[author]=' . $author . '&request[per_page]=30&request[fields][active_installs]=true'
);
$products = json_decode( wp_remote_retrieve_body( $products ) );
if ( is_object( $products ) ) {
$products = isset( $products->themes ) ? $products->themes : array();
} else {
$products = array();
}
return (array) $products;
}
/**
* Fetch plugin from wporg api.
*
* @param string $author The author slug.
*
* @return array The list of plugins for the selected author.
*/
function get_plugins_from_wporg( $author ) {
$products = wp_remote_get(
'https://api.wordpress.org/plugins/info/1.1/?action=query_plugins&request[author]=' . $author . '&request[per_page]=40&request[fields][active_installs]=true'
);
$products = json_decode( wp_remote_retrieve_body( $products ) );
if ( is_object( $products ) ) {
$products = isset( $products->plugins ) ? $products->plugins : array();
} else {
$products = array();
}
return (array) $products;
}
}

View File

@ -0,0 +1,358 @@
<?php
/**
* The class that exposes endpoints.
*
* @package ThemeIsleSDK
* @subpackage Rollback
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
// Exit if accessed directly.
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Loader;
use ThemeisleSDK\Product;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Expose endpoints for ThemeIsle SDK.
*/
class Endpoint extends Abstract_Module {
/**
* Endpoint slug.
*/
const SDK_ENDPOINT = 'themeisle-sdk';
/**
* Endpoint version.
*/
const SDK_ENDPOINT_VERSION = 1;
/**
* Hash file which contains the checksum.
*/
const HASH_FILE = 'themeisle-hash.json';
/*
* If true, the endpoint will expect a product slug and will return the value only for that.
*/
const PRODUCT_SPECIFIC = false;
/**
* Registers the endpoints.
*/
function rest_register() {
register_rest_route(
self::SDK_ENDPOINT . '/v' . self::SDK_ENDPOINT_VERSION,
'/checksum/' . ( self::PRODUCT_SPECIFIC ? '(?P<slug>.*)/' : '' ),
array(
'methods' => 'GET',
'callback' => array( $this, 'checksum' ),
)
);
}
/**
* The checksum endpoint.
*
* @param \WP_REST_Request $data the request.
*
* @return \WP_REST_Response Response or the error
*/
function checksum( \WP_REST_Request $data ) {
$products = Loader::get_products();
if ( self::PRODUCT_SPECIFIC ) {
$params = $this->validate_params( $data, array( 'slug' ) );
foreach ( $products as $product ) {
if ( $params['slug'] === $product->get_slug() ) {
$products = array( $product );
break;
}
}
}
$response = array();
$custom_css = $this->has_custom_css();
if ( is_bool( $custom_css ) ) {
$response['custom_css'] = $custom_css;
}
$response['child_theme'] = $this->get_theme_properties();
foreach ( $products as $product ) {
$files = array();
switch ( $product->get_type() ) {
case 'plugin':
$files = array();
break;
case 'theme':
$files = array( 'style.css', 'functions.php' );
break;
}
$error = '';
// if any element in the $files array contains a '/', this would imply recursion is required.
$diff = $this->generate_diff(
$product,
$files,
array_reduce(
$files,
array(
$this,
'is_recursion_required',
),
false
)
);
if ( is_wp_error( $diff ) ) {
/**
* Error returner by the diff checker method.
*
* @var \WP_Error $diff Error returned.
*/
$error = $diff->get_error_message();
$diff = array();
}
$response['products'][] = array(
'slug' => $product->get_slug(),
'version' => $product->get_version(),
'diffs' => $diff,
'error' => $error,
);
}
return new \WP_REST_Response( array( 'checksum' => $response ) );
}
/**
* Validates the parameters to the API
*
* @param \WP_REST_Request $data the request.
* @param array $params the parameters to validate.
*
* @return array of parameter name=>value
*/
private function validate_params( \WP_REST_Request $data, $params ) {
$collect = array();
foreach ( $params as $param ) {
$value = sanitize_text_field( $data[ $param ] );
if ( empty( $value ) ) {
return rest_ensure_response(
new \WP_Error(
'themeisle_' . $param . '_invalid',
sprintf( 'Invalid %', $param ),
array(
'status' => 403,
)
)
);
} else {
$collect[ $param ] = $value;
}
}
return $collect;
}
/**
* Check if custom css has been added to the theme.
*
* @return bool Whether custom css has been added to the theme.
*/
private function has_custom_css() {
$query = new \WP_Query(
array(
'post_type' => 'custom_css',
'post_status' => 'publish',
'numberposts' => 1,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
)
);
if ( $query->have_posts() ) {
$query->the_post();
$content = get_the_content();
// if the content contains a colon, a CSS rule has been added.
return strpos( $content, ':' ) === false ? false : true;
}
return false;
}
/**
* Get the current theme properties.
*
* @return mixed Properties of the current theme.
*/
function get_theme_properties() {
if ( ! is_child_theme() ) {
return false;
}
$properties = array();
$theme = wp_get_theme();
// @codingStandardsIgnoreStart
$properties['name'] = $theme->Name;
// @codingStandardsIgnoreEnd
// get the files in the child theme.
require_once( ABSPATH . 'wp-admin/includes/file.php' );
WP_Filesystem();
global $wp_filesystem;
$path = str_replace( ABSPATH, $wp_filesystem->abspath(), get_stylesheet_directory() );
$list = $wp_filesystem->dirlist( $path, false, false );
if ( $list ) {
$list = array_keys( self::flatten_dirlist( $list ) );
$properties['files'] = $list;
}
return $properties;
}
/**
* Flatten the results of WP_Filesystem::dirlist() for iterating over.
*
* @access private
*
* @param array $nested_files Array of files as returned by WP_Filesystem::dirlist().
* @param string $path Relative path to prepend to child nodes. Optional.
*
* @return array $files A flattened array of the $nested_files specified.
*/
private static function flatten_dirlist( $nested_files, $path = '' ) {
$files = array();
foreach ( $nested_files as $name => $details ) {
$files[ $path . $name ] = $details;
// Append children recursively.
if ( ! empty( $details['files'] ) ) {
$children = self::flatten_dirlist( $details['files'], $path . $name . '/' );
// Merge keeping possible numeric keys, which array_merge() will reindex from 0..n.
$files = $files + $children;
}
}
return $files;
}
/**
* Generate the diff of the files.
*
* @param Product $product Themeisle Product.
* @param array $files Array of files.
* @param bool $recurse Whether to recurse or not.
*
* @return mixed Diff data.
*/
private function generate_diff( $product, $files, $recurse = false ) {
require_once( ABSPATH . 'wp-admin/includes/file.php' );
WP_Filesystem();
global $wp_filesystem;
$diff = array();
$path = str_replace( ABSPATH, $wp_filesystem->abspath(), plugin_dir_path( $product->get_basefile() ) );
$list = $wp_filesystem->dirlist( $path, false, $recurse );
// nothing found.
if ( ! $list ) {
return $diff;
}
$list = array_keys( self::flatten_dirlist( $list ) );
// now let's get the valid files that actually exist.
if ( empty( $files ) ) {
$files = $list;
} else {
$files = array_intersect( $files, $list );
}
// fetch the calculated hashes.
if ( ! $wp_filesystem->is_readable( $path . '/' . self::HASH_FILE ) ) {
return new \WP_Error( 'themeisle_sdk_hash_not_found', sprintf( '%s not found', self::HASH_FILE ) );
}
$hashes = json_decode( $wp_filesystem->get_contents( $path . '/' . self::HASH_FILE ), true );
ksort( $hashes );
$diff = array();
foreach ( $files as $file ) {
// file does not exist in the hashes.
if ( ! array_key_exists( $file, $hashes ) ) {
continue;
}
$new = md5( $wp_filesystem->get_contents( $path . $file ) );
$old = $hashes[ $file ];
// same hash, bail.
if ( $new === $old ) {
continue;
}
$diff[] = $file;
}
return $diff;
}
/**
* Check if recursion needs to be enabled on the WP_Filesystem by reducing the array of files to a boolean.
*
* @param string $carry Value of the previous iteration.
* @param string $item Value of the current iteration.
*
* @return bool Whether to recurse or not.
*/
function is_recursion_required( $carry, $item ) {
if ( ! $carry ) {
return ( strpos( $item, '/' ) !== false );
}
return $carry;
}
/**
* Load module for this product.
*
* @param Product $product Product to check.
*
* @return bool Should we load this?
*/
public function can_load( $product ) {
return true;
}
/**
* Load module logic.
*
* @param Product $product Product to load.
*/
public function load( $product ) {
$this->setup_endpoints();
return $this;
}
/**
* Setup endpoints.
*/
private function setup_endpoints() {
global $wp_version;
if ( version_compare( $wp_version, '4.4', '<' ) ) {
// no REST support.
return;
}
$this->setup_rest();
}
/**
* Setup REST endpoints.
*/
private function setup_rest() {
add_action( 'rest_api_init', array( $this, 'rest_register' ) );
}
}

View File

@ -0,0 +1,798 @@
<?php
/**
* The main loader class for license handling.
*
* @package ThemeIsleSDK
* @subpackage Modules
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
// Exit if accessed directly.
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Product;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Licenser module for ThemeIsle SDK.
*/
class Licenser extends Abstract_Module {
/**
* Number of max failed checks before showing the license message.
*
* @var int $max_failed Maximum failed checks allowed before show the notice
*/
private static $max_failed = 5;
/**
* License key string.
*
* @var string $license_key The license key string
*/
public $license_key;
/**
* This ensures that the custom API request only runs on the second time that WP fires the update check.
*
* @var bool $do_check Flag for request.
*/
private $do_check = false;
/**
* Number of failed checks to the api endpoint.
*
* @var bool $failed_checks
*/
private $failed_checks = 0;
/**
* The product update response key.
*
* @var string $product_key Product key.
*/
private $product_key;
/**
* Disable wporg updates for premium products.
*
* @param string $r Update payload.
* @param string $url The api url.
*
* @return mixed List of themes to check for update.
*/
function disable_wporg_update( $r, $url ) {
if ( 0 !== strpos( $url, 'https://api.wordpress.org/themes/update-check/' ) ) {
return $r;
}
// Decode the JSON response.
$themes = json_decode( $r['body']['themes'] );
unset( $themes->themes->{$this->product->get_slug()} );
// Encode the updated JSON response.
$r['body']['themes'] = json_encode( $themes );
return $r;
}
/**
* Register the setting for the license of the product.
*
* @return bool
*/
public function register_settings() {
if ( ! is_admin() ) {
return false;
}
if ( apply_filters( $this->product->get_key() . '_hide_license_field', false ) ) {
return;
}
add_settings_field(
$this->product->get_key() . '_license',
$this->product->get_name() . ' license',
array( $this, 'license_view' ),
'general'
);
}
/**
* The license view field.
*/
public function license_view() {
$status = $this->get_license_status();
$value = $this->license_key;
$activate_string = apply_filters( $this->product->get_key() . '_lc_activate_string', 'Activate' );
$deactivate_string = apply_filters( $this->product->get_key() . '_lc_deactivate_string', 'Deactivate' );
$valid_string = apply_filters( $this->product->get_key() . '_lc_valid_string', 'Valid' );
$invalid_string = apply_filters( $this->product->get_key() . '_lc_invalid_string', 'Invalid' );
$license_message = apply_filters( $this->product->get_key() . '_lc_license_message', 'Enter your license from %s purchase history in order to get %s updates' );
$error_message = $this->get_error();
?>
<style type="text/css">
input.themeisle-sdk-text-input-valid {
border: 1px solid #7ad03a;
}
input.themeisle-sdk-license-input {
width: 300px;
padding: 0 8px;
line-height: 2;
min-height: 30px;
}
.themeisle-sdk-license-deactivate-cta {
color: #fff;
background: #7ad03a;
display: inline-block;
text-decoration: none;
font-size: 13px;
line-height: 30px;
height: 26px;
margin-left: 5px;
padding: 0 10px 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.themeisle-sdk-license-activate-cta {
color: #fff;
background: #dd3d36;
display: inline-block;
text-decoration: none;
font-size: 13px;
line-height: 30px;
height: 26px;
margin-left: 5px;
padding: 0 10px 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
}
button.button.themeisle-sdk-licenser-button-cta {
line-height: 26px;
height: 29px;
vertical-align: top;
}
</style>
<?php
echo sprintf(
'<p>%s<input class="themeisle-sdk-license-input %s" type="text" id="%s_license" name="%s_license" value="%s" /><a class="%s">%s</a>&nbsp;&nbsp;&nbsp;<button name="%s_btn_trigger" class="button button-primary themeisle-sdk-licenser-button-cta" value="yes" type="submit" >%s</button></p><p class="description">%s</p>%s',
( ( 'valid' === $status ) ? sprintf( '<input type="hidden" value="%s" name="%s_license" />', $value, $this->product->get_key() ) : '' ),
( ( 'valid' === $status ) ? 'themeisle-sdk-text-input-valid' : '' ),
$this->product->get_key(),
( ( 'valid' === $status ) ? $this->product->get_key() . '_mask' : $this->product->get_key() ),
( ( 'valid' === $status ) ? ( str_repeat( '*', 30 ) . substr( $value, - 5 ) ) : $value ),
( 'valid' === $status ? 'themeisle-sdk-license-deactivate-cta' : 'themeisle-sdk-license-activate-cta' ),
( 'valid' === $status ? $valid_string : $invalid_string ),
$this->product->get_key(),
( 'valid' === $status ? $deactivate_string : $activate_string ),
sprintf( $license_message, '<a href="' . $this->get_api_url() . '">' . $this->get_distributor_name() . '</a> ', $this->product->get_type() ),
empty( $error_message ) ? '' : sprintf( '<p style="color:#dd3d36">%s</p>', $error_message )
);
}
/**
* Return the license status.
*
* @return string The License status.
*/
public function get_license_status() {
$license_data = get_option( $this->product->get_key() . '_license_data', '' );
if ( '' === $license_data ) {
return get_option( $this->product->get_key() . '_license_status', 'not_active' );
}
return isset( $license_data->license ) ? $license_data->license : get_option( $this->product->get_key() . '_license_status', 'not_active' );
}
/**
* Get remote api url.
*
* @return string Remote api url.
*/
public function get_api_url() {
if ( $this->is_from_partner( $this->product ) ) {
return 'https://themeisle.com';
}
return $this->product->get_store_url();
}
/**
* Get remote api url.
*
* @return string Remote api url.
*/
public function get_distributor_name() {
if ( $this->is_from_partner( $this->product ) ) {
return 'ThemeIsle';
}
return $this->product->get_store_name();
}
/**
* Show the admin notice regarding the license status.
*
* @return bool Should we show the notice ?
*/
function show_notice() {
if ( ! is_admin() ) {
return false;
}
if ( apply_filters( $this->product->get_key() . '_hide_license_notices', false ) ) {
return;
}
$status = $this->get_license_status();
$no_activations_string = apply_filters( $this->product->get_key() . '_lc_no_activations_string', 'No activations left for %s !!!. You need to upgrade your plan in order to use %s on more websites. Please ask the %s Staff for more details.' );
$no_valid_string = apply_filters( $this->product->get_key() . '_lc_no_valid_string', 'In order to benefit from updates and support for %s, please add your license code from your <a href="%s" target="_blank">purchase history</a> and validate it <a href="%s">here</a>. ' );
// No activations left for this license.
if ( 'valid' != $status && $this->check_activation() ) {
?>
<div class="error">
<p><strong>
<?php
echo sprintf(
$no_activations_string,
$this->product->get_name(),
$this->product->get_name(),
'<a href="' . $this->get_api_url() . '" target="_blank">' . $this->get_distributor_name() . '</a>'
);
?>
</strong>
</p>
</div>
<?php
return false;
}
// Invalid license key.
if ( 'valid' != $status ) {
?>
<div class="error">
<p>
<strong><?php echo sprintf( $no_valid_string, $this->product->get_name() . ' ' . $this->product->get_type(), $this->get_api_url(), admin_url( 'options-general.php' ) . '#' . $this->product->get_key() . '_license' ); ?> </strong>
</p>
</div>
<?php
return false;
}
return true;
}
/**
* Check if the license is active or not.
*
* @return bool
*/
public function check_activation() {
$license_data = get_option( $this->product->get_key() . '_license_data', '' );
if ( '' === $license_data ) {
return false;
}
return isset( $license_data->error ) ? ( 'no_activations_left' == $license_data->error ) : false;
}
/**
* Check if the license is about to expire in the next month.
*
* @return bool
*/
function check_expiration() {
$license_data = get_option( $this->product->get_key() . '_license_data', '' );
if ( '' === $license_data ) {
return false;
}
if ( ! isset( $license_data->expires ) ) {
return false;
}
if ( strtotime( $license_data->expires ) - time() > 30 * 24 * 3600 ) {
return false;
}
return true;
}
/**
* Return the renew url from the store used.
*
* @return string The renew url.
*/
function renew_url() {
$license_data = get_option( $this->product->get_key() . '_license_data', '' );
if ( '' === $license_data ) {
return $this->get_api_url();
}
if ( ! isset( $license_data->download_id ) || ! isset( $license_data->key ) ) {
return $this->get_api_url();
}
return $this->get_api_url() . '/checkout/?edd_license_key=' . $license_data->key . '&download_id=' . $license_data->download_id;
}
/**
* Run the license check call.
*/
public function product_valid() {
if ( false !== ( $license = get_transient( $this->product->get_key() . '_license_data' ) ) ) {
return;
}
$license = $this->check_license();
set_transient( $this->product->get_key() . '_license_data', $license, 12 * HOUR_IN_SECONDS );
update_option( $this->product->get_key() . '_license_data', $license );
}
/**
* Check the license status.
*
* @return object The license data.
*/
public function check_license() {
$status = $this->get_license_status();
if ( 'not_active' == $status ) {
$license_data = new \stdClass();
$license_data->license = 'not_active';
return $license_data;
}
$license = trim( $this->license_key );
$api_params = array(
'edd_action' => 'check_license',
'license' => $license,
'item_name' => rawurlencode( $this->product->get_name() ),
'url' => rawurlencode( home_url() ),
);
// Call the custom API.
$response = wp_remote_get(
add_query_arg( $api_params, $this->get_api_url() ),
array(
'timeout' => 15,
'sslverify' => false,
)
);
if ( is_wp_error( $response ) ) {
$license_data = new \stdClass();
$license_data->license = 'valid';
} else {
$license_data = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! is_object( $license_data ) ) {
$license_data = new \stdClass();
$license_data->license = 'valid';
}
}
$license_old = get_option( $this->product->get_key() . '_license_data', '' );
if ( 'valid' == $license_old->license && ( $license_data->license != $license_old->license ) ) {
$this->increment_failed_checks();
} else {
$this->reset_failed_checks();
}
if ( $this->failed_checks <= self::$max_failed ) {
return $license_old;
}
if ( isset( $license_old->hide_valid ) ) {
$license_data->hide_valid = true;
}
if ( ! isset( $license_data->key ) ) {
$license_data->key = isset( $license_old->key ) ? $license_old->key : '';
}
if ( isset( $license_old->hide_expiration ) ) {
$license_data->hide_expiration = true;
}
if ( isset( $license_old->hide_activation ) ) {
$license_data->hide_activation = true;
}
return $license_data;
}
/**
* Increment the failed checks.
*/
private function increment_failed_checks() {
$this->failed_checks ++;
update_option( $this->product->get_key() . '_failed_checks', $this->failed_checks );
}
/**
* Reset the failed checks
*/
private function reset_failed_checks() {
$this->failed_checks = 1;
update_option( $this->product->get_key() . '_failed_checks', $this->failed_checks );
}
/**
* Set license validation error message.
*
* @param string $message Error message.
*/
public function set_error( $message = '' ) {
set_transient( $this->product->get_key() . 'act_err', $message, MINUTE_IN_SECONDS );
return;
}
/**
* Return the last error message.
*
* @return mixed Error message.
*/
public function get_error() {
return get_transient( $this->product->get_key() . 'act_err' );
}
/**
* Activate the license remotely.
*/
function activate_license() {
// listen for our activate button to be clicked.
if ( ! isset( $_POST[ $this->product->get_key() . '_btn_trigger' ] ) ) {
return;
}
$status = $this->get_license_status();
// retrieve the license from the database.
$license = $_POST[ $this->product->get_key() . '_license' ];
$api_params = array(
'license' => $license,
'item_name' => rawurlencode( $this->product->get_name() ),
'url' => rawurlencode( home_url() ),
);
if ( 'valid' != $status ) {
// data to send in our API request.
$api_params['edd_action'] = 'activate_license';
} else {
$api_params['edd_action'] = 'deactivate_license';
}
// Call the custom API.
$response = wp_remote_get( add_query_arg( $api_params, $this->get_api_url() ) );
// make sure the response came back okay.
if ( is_wp_error( $response ) ) {
$this->set_error( sprintf( 'ERROR: Failed to connect to the license service. Please try again later. Reason: %s', $response->get_error_message() ) );
return;
}
$license_data = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! is_object( $license_data ) ) {
$this->set_error( 'ERROR: Failed to validate license. Please try again in one minute.' );
return;
}
if ( ! isset( $license_data->license ) ) {
$license_data->license = 'invalid';
}
if ( ! isset( $license_data->key ) ) {
$license_data->key = $license;
}
if ( 'valid' == $license_data->license ) {
$this->reset_failed_checks();
}
$this->set_error( '' );
if ( 'deactivate_license' === $api_params['edd_action'] ) {
delete_option( $this->product->get_key() . '_license_data' );
delete_option( $this->product->get_key() . '_license_plan' );
delete_transient( $this->product->get_key() . '_license_data' );
return;
}
if ( isset( $license_data->plan ) ) {
update_option( $this->product->get_key() . '_license_plan', $license_data->plan );
}
update_option( $this->product->get_key() . '_license_data', $license_data );
set_transient( $this->product->get_key() . '_license_data', $license_data, 12 * HOUR_IN_SECONDS );
}
/**
* Load the Themes screen.
*/
function load_themes_screen() {
add_thickbox();
add_action( 'admin_notices', array( &$this, 'update_nag' ) );
}
/**
* Alter the nag for themes update.
*/
function update_nag() {
$theme = wp_get_theme( $this->product->get_slug() );
$api_response = get_transient( $this->product_key );
if ( false === $api_response || ! isset( $api_response->new_version ) ) {
return;
}
$update_url = wp_nonce_url( 'update.php?action=upgrade-theme&amp;theme=' . urlencode( $this->product->get_slug() ), 'upgrade-theme_' . $this->product->get_slug() );
$update_message = apply_filters( 'themeisle_sdk_license_update_message', 'Updating this theme will lose any customizations you have made. Cancel to stop, OK to update.' );
$update_onclick = ' onclick="if ( confirm(\'' . esc_js( $update_message ) . '\') ) {return true;}return false;"';
if ( version_compare( $this->product->get_version(), $api_response->new_version, '<' ) ) {
echo '<div id="update-nag">';
printf(
'<strong>%1$s %2$s</strong> is available. <a href="%3$s" class="thickbox" title="%4s">Check out what\'s new</a> or <a href="%5$s"%6$s>update now</a>.',
$theme->get( 'Name' ),
$api_response->new_version,
sprintf( '%s&TB_iframe=true&amp;width=1024&amp;height=800', $this->product->get_changelog() ),
$theme->get( 'Name' ),
$update_url,
$update_onclick
);
echo '</div>';
echo '<div id="' . $this->product->get_slug() . '_' . 'changelog" style="display:none;">';
echo wpautop( $api_response->sections['changelog'] );
echo '</div>';
}
}
/**
* Alter update transient.
*
* @param mixed $value The transient data.
*
* @return mixed
*/
function theme_update_transient( $value ) {
$update_data = $this->check_for_update();
if ( $update_data ) {
$value->response[ $this->product->get_slug() ] = $update_data;
}
return $value;
}
/**
* Check for updates
*
* @return array|bool Either the update data or false in case of failure.
*/
function check_for_update() {
$update_data = get_transient( $this->product_key );
if ( false === $update_data ) {
$failed = false;
$update_data = $this->get_version_data();
if ( empty( $update_data ) ) {
$failed = true;
}
// If the response failed, try again in 30 minutes.
if ( $failed ) {
$data = new \stdClass();
$data->new_version = $this->product->get_version();
set_transient( $this->product_key, $data, 30 * MINUTE_IN_SECONDS );
return false;
}
$update_data->sections = isset( $update_data->sections ) ? maybe_unserialize( $update_data->sections ) : null;
set_transient( $this->product_key, $update_data, 12 * HOUR_IN_SECONDS );
}
if ( ! isset( $update_data->new_version ) ) {
return false;
}
if ( version_compare( $this->product->get_version(), $update_data->new_version, '>=' ) ) {
return false;
}
return (array) $update_data;
}
/**
* Check remote api for latest version.
*
* @return bool|mixed Update api response.
*/
private function get_version_data() {
$api_params = array(
'edd_action' => 'get_version',
'version' => $this->product->get_version(),
'license' => empty( $this->license_key ) ? 'free' : $this->license_key,
'name' => rawurlencode( $this->product->get_name() ),
'slug' => $this->product->get_slug(),
'author' => rawurlencode( $this->get_distributor_name() ),
'url' => rawurlencode( home_url() ),
);
$response = wp_remote_get(
add_query_arg( $api_params, $this->get_api_url() ),
array(
'timeout' => 15,
'sslverify' => false,
)
);
if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) ) {
return false;
}
$update_data = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! is_object( $update_data ) ) {
return false;
}
return $update_data;
}
/**
* Delete the update transient
*/
function delete_theme_update_transient() {
delete_transient( $this->product_key );
}
/**
* Check for Updates at the defined API endpoint and modify the update array.
*
* @param array $_transient_data Update array build by WordPress.
*
* @return mixed Modified update array with custom plugin data.
*/
public function pre_set_site_transient_update_plugins_filter( $_transient_data ) {
if ( empty( $_transient_data ) || ! $this->do_check ) {
$this->do_check = true;
return $_transient_data;
}
$api_response = $this->api_request();
if ( false !== $api_response && is_object( $api_response ) && isset( $api_response->new_version ) ) {
if ( version_compare( $this->product->get_version(), $api_response->new_version, '<' ) ) {
$_transient_data->response[ $this->product->get_slug() . '/' . $this->product->get_file() ] = $api_response;
}
}
return $_transient_data;
}
/**
* Calls the API and, if successfull, returns the object delivered by the API.
*
* @param string $_action The requested action.
* @param array $_data Parameters for the API action.
*
* @return false||object
*/
private function api_request( $_action = '', $_data = '' ) {
$update_data = $this->get_version_data();
if ( empty( $update_data ) ) {
return false;
}
if ( $update_data && isset( $update_data->sections ) ) {
$update_data->sections = maybe_unserialize( $update_data->sections );
}
return $update_data;
}
/**
* Updates information on the "View version x.x details" page with custom data.
*
* @param mixed $_data Plugin data.
* @param string $_action Action to send.
* @param object $_args Arguments to use.
*
* @return object $_data
*/
public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
if ( ( 'plugin_information' != $_action ) || ! isset( $_args->slug ) || ( $_args->slug != $this->product->get_slug() ) ) {
return $_data;
}
$api_response = $this->api_request();
if ( false !== $api_response ) {
$_data = $api_response;
}
return $_data;
}
/**
* Disable SSL verification in order to prevent download update failures.
*
* @param array $args Http args.
* @param string $url Url to check.
*
* @return array $array
*/
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;
}
/**
* Check if we should load the module for this product.
*
* @param Product $product Product data.
*
* @return bool Should we load the module?
*/
public function can_load( $product ) {
if ( $product->is_wordpress_available() ) {
return false;
}
return ( apply_filters( $product->get_key() . '_enable_licenser', true ) === true );
}
/**
* Load module logic.
*
* @param Product $product Product to load the module for.
*
* @return Licenser Module object.
*/
public function load( $product ) {
$this->product = $product;
$this->product_key = $this->product->get_key() . '-update-response';
$this->license_key = $this->product->get_license();
if ( $this->product->requires_license() ) {
$this->failed_checks = intval( get_option( $this->product->get_key() . '_failed_checks', 0 ) );
$this->register_license_hooks();
}
if ( $this->product->is_plugin() ) {
add_filter(
'pre_set_site_transient_update_plugins',
[
$this,
'pre_set_site_transient_update_plugins_filter',
]
);
add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
add_filter( 'http_request_args', array( $this, 'http_request_args' ), 10, 2 );
return $this;
}
if ( $this->product->is_theme() ) {
add_filter( 'site_transient_update_themes', array( &$this, 'theme_update_transient' ) );
add_filter( 'delete_site_transient_update_themes', array( &$this, 'delete_theme_update_transient' ) );
add_action( 'load-update-core.php', array( &$this, 'delete_theme_update_transient' ) );
add_action( 'load-themes.php', array( &$this, 'delete_theme_update_transient' ) );
add_action( 'load-themes.php', array( &$this, 'load_themes_screen' ) );
add_filter( 'http_request_args', array( $this, 'disable_wporg_update' ), 5, 2 );
return $this;
}
return $this;
}
/**
* Register license fields for the products.
*/
public function register_license_hooks() {
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_init', array( $this, 'activate_license' ) );
add_action( 'admin_init', array( $this, 'product_valid' ), 99999999 );
add_action( 'admin_notices', array( $this, 'show_notice' ) );
add_filter( $this->product->get_key() . '_license_status', array( $this, 'get_license_status' ) );
}
}

View File

@ -0,0 +1,177 @@
<?php
/**
* The logger model class for ThemeIsle SDK
*
* @package ThemeIsleSDK
* @subpackage Modules
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Loader;
use ThemeisleSDK\Product;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Logger module for ThemeIsle SDK.
*/
class Logger extends Abstract_Module {
/**
* Endpoint where to collect logs.
*/
const TRACKING_ENDPOINT = 'http://log.themeisle.com/wp-json/v1/logs/';
/**
* Check if we should load the module for this product.
*
* @param Product $product Product to load the module for.
*
* @return bool Should we load ?
*/
public function can_load( $product ) {
return apply_filters( $product->get_slug() . '_sdk_enable_logger', true );
}
/**
* Load module logic.
*
* @param Product $product Product to load.
*
* @return Logger Module object.
*/
public function load( $product ) {
$this->product = $product;
$this->setup_notification();
$this->setup_actions();
return $this;
}
/**
* Setup notification on admin.
*/
public function setup_notification() {
if ( ! $this->product->is_wordpress_available() ) {
return;
}
add_filter( 'themeisle_sdk_registered_notifications', [ $this, 'add_notification' ] );
}
/**
* Setup tracking actions.
*/
public function setup_actions() {
if ( ! $this->is_logger_active() ) {
return;
}
$action_key = $this->product->get_key() . '_log_activity';
if ( ! wp_next_scheduled( $action_key ) ) {
wp_schedule_single_event( time() + ( rand( 1, 24 ) * 3600 ), $action_key );
}
add_action( $action_key, array( $this, 'send_log' ) );
}
/**
* Check if the logger is active.
*
* @return bool Is logger active?
*/
private function is_logger_active() {
if ( ! $this->product->is_wordpress_available() ) {
return true;
}
$pro_slug = $this->product->get_pro_slug();
if ( ! empty( $pro_slug ) ) {
$all_products = Loader::get_products();
if ( isset( $all_products[ $pro_slug ] ) ) {
return true;
}
}
return ( get_option( $this->product->get_key() . '_logger_flag', 'no' ) === 'yes' );
}
/**
* Add notification to queue.
*
* @param array $all_notifications Previous notification.
*
* @return array All notifications.
*/
public function add_notification( $all_notifications ) {
$message = apply_filters( $this->product->get_key() . '_logger_heading', 'Do you enjoy <b>{product}</b>? Become a contributor by opting in to our anonymous data tracking. We guarantee no sensitive data is collected.' );
$message = str_replace(
array( '{product}' ),
$this->product->get_friendly_name(),
$message
);
$button_submit = apply_filters( $this->product->get_key() . '_logger_button_submit', 'Sure, I would love to help.' );
$button_cancel = apply_filters( $this->product->get_key() . '_logger_button_cancel', 'No, thanks.' );
$all_notifications[] = [
'id' => $this->product->get_key() . '_logger_flag',
'message' => $message,
'ctas' => [
'confirm' => [
'link' => '#',
'text' => $button_submit,
],
'cancel' => [
'link' => '#',
'text' => $button_cancel,
],
],
];
return $all_notifications;
}
/**
* Send the statistics to the api endpoint.
*/
public function send_log() {
$environment = array();
$theme = wp_get_theme();
$environment['theme'] = array();
$environment['theme']['name'] = $theme->get( 'Name' );
$environment['theme']['author'] = $theme->get( 'Author' );
$environment['plugins'] = get_option( 'active_plugins' );
global $wp_version;
wp_remote_post(
self::TRACKING_ENDPOINT,
array(
'method' => 'POST',
'timeout' => 3,
'redirection' => 5,
'headers' => array(
'X-ThemeIsle-Event' => 'log_site',
),
'body' => array(
'site' => get_site_url(),
'slug' => $this->product->get_slug(),
'version' => $this->product->get_version(),
'wp_version' => $wp_version,
'data' => apply_filters( $this->product->get_key() . '_logger_data', array() ),
'environment' => $environment,
'license' => apply_filters( $this->product->get_key() . '_license_status', '' ),
),
)
);
}
}

View File

@ -0,0 +1,459 @@
<?php
/**
* The notification model class for ThemeIsle SDK
*
* @package ThemeIsleSDK
* @subpackage Modules
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Product;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Notification module for ThemeIsle SDK.
*/
class Notification extends Abstract_Module {
/**
* Show notifications only after the user has the product installed after this amount of time, in hours.
*/
const MIN_INSTALL_TIME = 100;
/**
* How much time should we show the notification, in days.
*/
const MAX_TIME_TO_LIVE = 7;
/**
* Number of days between notifications.
*/
const TIME_BETWEEN_NOTIFICATIONS = 5;
/**
* Holds a possible notification list.
*
* @var array Notifications list.
*/
private static $notifications = [];
/**
* Show notification data.
*/
public static function show_notification() {
$current_notification = self::get_last_notification();
$notification_details = [];
// Check if the saved notification is still present among the possible ones.
if ( ! empty( $current_notification ) ) {
$notification_details = self::get_notification_details( $current_notification );
if ( empty( $notification_details ) ) {
$current_notification = [];
}
}
// Check if the notificatin is expired.
if ( ! empty( $current_notification ) && self::is_notification_expired( $current_notification ) ) {
update_option( $current_notification['id'], 'no' );
self::set_last_active_notification_timestamp();
$current_notification = [];
}
// If we don't have any saved notification, get a new one.
if ( empty( $current_notification ) ) {
$notification_details = self::get_random_notification();
if ( empty( $notification_details ) ) {
return;
}
self::set_active_notification(
[
'id' => $notification_details['id'],
'display_at' => time(),
]
);
}
if ( empty( $notification_details ) ) {
return;
}
$notification_html = self::get_notification_html( $notification_details );
do_action( $notification_details['id'] . '_before_render' );
echo $notification_html;
do_action( $notification_details['id'] . '_after_render' );
self::render_snippets();
}
/**
* Get last notification details.
*
* @return array Last notification details.
*/
private static function get_last_notification() {
$notification = self::get_notifications_metadata();
return isset( $notification['last_notification'] ) ? $notification['last_notification'] : [];
}
/**
* Get notification center details.
*
* @return array Notification center details.
*/
private static function get_notifications_metadata() {
$data = get_option(
'themeisle_sdk_notifications',
[
'last_notification' => [],
'last_notification_active' => 0,
]
);
return $data;
}
/**
* Check if the notification is still possible.
*
* @param array $notification Notification to check.
*
* @return array Either is still active or not.
*/
private static function get_notification_details( $notification ) {
$notifications = array_filter(
self::$notifications,
function ( $value ) use ( $notification ) {
if ( isset( $value['id'] ) && isset( $notification['id'] ) && $value['id'] === $notification['id'] ) {
return true;
}
return false;
}
);
return ! empty( $notifications ) ? reset( $notifications ) : [];
}
/**
* Check if the notification is expired.
*
* @param array $notification Notification to check.
*
* @return bool Either the notification is due.
*/
private static function is_notification_expired( $notification ) {
if ( ! isset( $notification['display_at'] ) ) {
return true;
}
$notifications = array_filter(
self::$notifications,
function ( $value ) use ( $notification ) {
if ( isset( $value['id'] ) && isset( $notification['id'] ) && $value['id'] === $notification['id'] ) {
return true;
}
return false;
}
);
if ( empty( $notifications ) ) {
return true;
}
$notification_definition = reset( $notifications );
$when_to_expire = isset( $notification_definition['expires_at'] )
? $notification_definition['expires_at'] :
( isset( $notification_definition['expires'] )
? ( $notification['display_at'] + $notification_definition['expires'] ) :
( $notification['display_at'] + self::MAX_TIME_TO_LIVE * DAY_IN_SECONDS )
);
return ( $when_to_expire - time() ) < 0;
}
/**
* Set last notification details.
*/
private static function set_last_active_notification_timestamp() {
$metadata = self::get_notifications_metadata();
$metadata['last_notification_active'] = time();
update_option( 'themeisle_sdk_notifications', $metadata );
}
/**
* Return notification to show.
*
* @return array Notification data.
*/
public static function get_random_notification() {
if ( ( time() - self::get_last_active_notification_timestamp() ) < self::TIME_BETWEEN_NOTIFICATIONS * DAY_IN_SECONDS ) {
return [];
}
$notifications = self::$notifications;
$notifications = array_filter(
$notifications,
function ( $value ) {
if ( isset( $value['sticky'] ) && true === $value['sticky'] ) {
return true;
}
return false;
}
);
// No priority notifications, use all.
if ( empty( $notifications ) ) {
$notifications = self::$notifications;
}
if ( empty( $notifications ) ) {
return [];
}
$notifications = array_values( $notifications );
return $notifications[ array_rand( $notifications, 1 ) ];
}
/**
* Get last notification details.
*
* @return array Last notification details.
*/
private static function get_last_active_notification_timestamp() {
$notification = self::get_notifications_metadata();
return isset( $notification['last_notification_active'] ) ? $notification['last_notification_active'] : 0;
}
/**
* Get last notification details.
*
* @param array $notification Notification data.
*/
private static function set_active_notification( $notification ) {
$metadata = self::get_notifications_metadata();
$metadata['last_notification'] = $notification;
update_option( 'themeisle_sdk_notifications', $metadata );
}
/**
* Get notification html.
*
* @param array $notification_details Notification details.
*
* @return string Html for notice.
*/
public static function get_notification_html( $notification_details ) {
$default = [
'id' => '',
'heading' => '',
'message' => '',
'ctas' => [
'confirm' => [
'link' => '#',
'text' => '',
],
'cancel' => [
'link' => '#',
'text' => '',
],
],
];
$notification_details = wp_parse_args( $notification_details, $default );
$notification_html = '<div class="notice notice-success is-dismissible themeisle-sdk-notice" data-notification-id="' . esc_attr( $notification_details['id'] ) . '" id="' . esc_attr( $notification_details['id'] ) . '-notification"> <div class="themeisle-sdk-notification-box">';
if ( ! empty( $notification_details['heading'] ) ) {
$notification_html .= sprintf( '<h4>%s</h4>', wp_kses_post( $notification_details['heading'] ) );
}
if ( ! empty( $notification_details['message'] ) ) {
$notification_html .= wp_kses_post( $notification_details['message'] );
}
$notification_html .= '<div class="actions">';
if ( ! empty( $notification_details['ctas']['confirm']['text'] ) ) {
$notification_html .= sprintf(
'<a href="%s" target="_blank" class=" button button-primary %s" data-confirm="yes" >%s</a>',
esc_url( $notification_details['ctas']['confirm']['link'] ),
esc_attr( $notification_details['id'] . '_confirm' ),
wp_kses_post( $notification_details['ctas']['confirm']['text'] )
);
}
if ( ! empty( $notification_details['ctas']['cancel']['text'] ) ) {
$notification_html .= sprintf(
'<a href="%s" class=" button %s" data-confirm="no">%s</a>',
esc_url( $notification_details['ctas']['cancel']['link'] ),
esc_attr( $notification_details['id'] ) . '_cancel',
wp_kses_post( $notification_details['ctas']['cancel']['text'] )
);
}
$notification_html .= '</div>';
$notification_html .= ' </div>';
$notification_html .= ' </div>';
return $notification_html;
}
/**
* Adds js snippet for hiding the notice.
*/
public static function render_snippets() {
?>
<style type="text/css">
.themeisle-sdk-notification-box {
padding: 3px;
}
.themeisle-sdk-notification-box .actions {
margin-top: 6px;
margin-bottom: 4px;
}
.themeisle-sdk-notification-box .button {
margin-right: 5px;
}
</style>
<script type="text/javascript">
(function ($) {
$(document).ready(function () {
$('#wpbody-content').on('click', ".themeisle-sdk-notice a.button, .themeisle-sdk-notice .notice-dismiss", function (e) {
var container = $('.themeisle-sdk-notice');
var link = $(this);
var notification_id = container.attr('data-notification-id');
var confirm = link.attr('data-confirm');
if (typeof confirm === "undefined") {
confirm = 'no';
}
$.post(
ajaxurl,
{
'nonce': '<?php echo wp_create_nonce( (string) __CLASS__ ); ?>',
'action': 'themeisle_sdk_dismiss_notice',
'id': notification_id,
'confirm': confirm
}
);
if (confirm === 'yes') {
$(this).trigger('themeisle-sdk:confirmed');
} else {
$(this).trigger('themeisle-sdk:canceled');
}
container.hide();
if (link.attr('href') === '#') {
return false;
}
});
});
})(jQuery);
</script>
<?php
}
/**
* Dismiss the notification.
*/
static function dismiss() {
check_ajax_referer( (string) __CLASS__, 'nonce' );
$id = isset( $_POST['id'] ) ? sanitize_text_field( $_POST['id'] ) : '';
$confirm = isset( $_POST['confirm'] ) ? sanitize_text_field( $_POST['confirm'] ) : 'no';
if ( empty( $id ) ) {
wp_send_json( [] );
}
self::set_last_active_notification_timestamp();
update_option( $id, $confirm );
do_action( $id . '_process_confirm', $confirm );
wp_send_json( [] );
}
/**
* Check if we should load the notification module.
*
* @param Product $product Product to check.
*
* @return bool Should we load this?
*/
public function can_load( $product ) {
if ( $this->is_from_partner( $product ) ) {
return false;
}
if ( ! current_user_can( 'manage_options' ) ) {
return false;
}
if ( ( time() - $product->get_install_time() ) < ( self::MIN_INSTALL_TIME * HOUR_IN_SECONDS ) ) {
return false;
}
return true;
}
/**
* Setup notifications queue.
*/
public static function setup_notifications() {
$notifications = apply_filters( 'themeisle_sdk_registered_notifications', [] );
$notifications = array_filter(
$notifications,
function ( $value ) {
if ( ! isset( $value['id'] ) ) {
return false;
}
if ( get_option( $value['id'], '' ) !== '' ) {
return false;
}
return apply_filters( $value['id'] . '_should_show', true );
}
);
self::$notifications = $notifications;
}
/**
* Load the module logic.
*
* @param Product $product Product to load the module for.
*
* @return Notification Module instance.
*/
public function load( $product ) {
if ( apply_filters( 'themeisle_sdk_hide_notifications', false ) ) {
return;
}
$this->product = $product;
$notifications = apply_filters( 'themeisle_sdk_registered_notifications', [] );
$notifications = array_filter(
$notifications,
function ( $value ) {
if ( ! isset( $value['id'] ) ) {
return false;
}
if ( get_option( $value['id'], '' ) !== '' ) {
return false;
}
return apply_filters( $value['id'] . '_should_show', true );
}
);
self::$notifications = $notifications;
add_action( 'admin_notices', array( __CLASS__, 'show_notification' ) );
add_action( 'wp_ajax_themeisle_sdk_dismiss_notice', array( __CLASS__, 'dismiss' ) );
add_action( 'admin_head', array( __CLASS__, 'setup_notifications' ) );
return $this;
}
}

View File

@ -0,0 +1,374 @@
<?php
/**
* The class that exposes hooks for recommend.
*
* @package ThemeIsleSDK
* @subpackage Rollback
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
// Exit if accessed directly.
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Product;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Expose endpoints for ThemeIsle SDK.
*/
class Recommendation extends Abstract_Module {
/**
* Load module logic.
*
* @param Product $product Product to load.
*/
public function load( $product ) {
$this->product = $product;
$this->setup_hooks();
return $this;
}
/**
* Setup endpoints.
*/
private function setup_hooks() {
add_action( $this->product->get_key() . '_recommend_products', array( $this, 'render_products_box' ), 10, 4 );
add_action( 'admin_head', array( $this, 'enqueue' ) );
}
/**
* Check if we should load the module for this product.
*
* @param Product $product Product data.
*
* @return bool Should we load the module?
*/
public function can_load( $product ) {
return true;
}
/**
* Render products box content.
*
* @param array $plugins_list - list of useful plugins (in slug => nicename format).
* @param array $themes_list - list of useful themes (in slug => nicename format).
* @param array $strings - list of translated strings.
* @param array $preferences - list of preferences.
*/
function render_products_box( $plugins_list, $themes_list, $strings, $preferences = array() ) {
if ( empty( $plugins_list ) && empty( $themes_list ) ) {
return;
}
if ( ! empty( $plugins_list ) && ! current_user_can( 'install_plugins' ) ) {
return;
}
if ( ! empty( $themes_list ) && ! current_user_can( 'install_themes' ) ) {
return;
}
add_thickbox();
if ( ! empty( $themes_list ) ) {
$list = $this->get_themes( $themes_list, $preferences );
if ( has_action( $this->product->get_key() . '_recommend_products_theme_template' ) ) {
do_action( $this->product->get_key() . '_recommend_products_theme_template', $list, $strings, $preferences );
} else {
echo '<div class="recommend-product">';
foreach ( $list as $theme ) {
echo '<div class="plugin_box">';
echo ' <img class="theme-banner" src="' . $theme->screenshot_url . '">';
echo ' <div class="title-action-wrapper">';
echo ' <span class="plugin-name">' . esc_html( $theme->custom_name ) . '</span>';
if ( ! isset( $preferences['description'] ) || ( isset( $preferences['description'] ) && $preferences['description'] ) ) {
echo '<span class="plugin-desc">' . esc_html( substr( $theme->description, 0, strpos( $theme->description, '.' ) ) ) . '.</span>';
}
echo ' </div>';
echo '<div class="plugin-box-footer">';
echo ' <div class="button-wrap">';
echo ' <a class="button button-primary " href="' . esc_url( $theme->custom_url ) . '"><span class="dashicons dashicons-external"></span>' . esc_html( $strings['install'] ) . '</a>';
echo ' </div>';
echo ' </div>';
echo '</div>';
}
echo '</div>';
}
}
if ( ! empty( $plugins_list ) ) {
$list = $this->get_plugins( $plugins_list, $preferences );
if ( has_action( $this->product->get_key() . '_recommend_products_plugin_template' ) ) {
do_action( $this->product->get_key() . '_recommend_products_plugin_template', $list, $strings, $preferences );
} else {
echo '<div class="recommend-product">';
foreach ( $list as $current_plugin ) {
echo '<div class="plugin_box">';
echo ' <img class="plugin-banner" src="' . $current_plugin->custom_image . '">';
echo ' <div class="title-action-wrapper">';
echo ' <span class="plugin-name">' . esc_html( $current_plugin->custom_name ) . '</span>';
if ( ! isset( $preferences['description'] ) || ( isset( $preferences['description'] ) && $preferences['description'] ) ) {
echo '<span class="plugin-desc">' . esc_html( substr( $current_plugin->short_description, 0, strpos( $current_plugin->short_description, '.' ) ) ) . '. </span>';
}
echo ' </div>';
echo ' <div class="plugin-box-footer">';
echo ' <a class="button button-primary thickbox open-plugin-details-modal" href="' . esc_url( $current_plugin->custom_url ) . '"><span class="dashicons dashicons-external"></span>' . esc_html( $strings['install'] ) . '</a>';
echo ' </div>';
echo '</div>';
}
echo '</div>';
}
}
}
/**
* Collect all the information for the themes list.
*
* @param array $themes_list - list of useful themes (in slug => nicename format).
* @param array $preferences - list of preferences.
*
* @return array
*/
private function get_themes( $themes_list, $preferences ) {
$list = array();
foreach ( $themes_list as $slug => $nicename ) {
$theme = $this->call_theme_api( $slug );
if ( ! $theme ) {
continue;
}
$url = add_query_arg(
array(
'theme' => $theme->slug,
),
network_admin_url( 'theme-install.php' )
);
$name = empty( $nicename ) ? $theme->name : $nicename;
$theme->custom_url = $url;
$theme->custom_name = $name;
$list[] = $theme;
}
return $list;
}
/**
* Call theme api
*
* @param string $slug theme slug.
*
* @return array|mixed|object
*/
private function call_theme_api( $slug ) {
$theme = get_transient( 'ti_theme_info_' . $slug );
if ( false !== $theme ) {
return $theme;
}
$products = wp_remote_get(
'https://api.wordpress.org/themes/info/1.1/?action=query_themes&request[theme]=' . $slug . '&request[per_page]=1'
);
$products = json_decode( wp_remote_retrieve_body( $products ) );
if ( is_object( $products ) ) {
$theme = $products->themes[0];
set_transient( 'ti_theme_info_' . $slug, $theme, 6 * HOUR_IN_SECONDS );
}
return $theme;
}
/**
* Collect all the information for the plugins list.
*
* @param array $plugins_list - list of useful plugins (in slug => nicename format).
* @param array $preferences - list of preferences.
*
* @return array
*/
private function get_plugins( $plugins_list, $preferences ) {
$list = array();
foreach ( $plugins_list as $plugin => $nicename ) {
$current_plugin = $this->call_plugin_api( $plugin );
$name = empty( $nicename ) ? $current_plugin->name : $nicename;
$image = $current_plugin->banners['low'];
if ( isset( $preferences['image'] ) && 'icon' === $preferences['image'] ) {
$image = $current_plugin->icons['1x'];
}
$url = add_query_arg(
array(
'tab' => 'plugin-information',
'plugin' => $current_plugin->slug,
'TB_iframe' => true,
'width' => 800,
'height' => 800,
),
network_admin_url( 'plugin-install.php' )
);
$current_plugin->custom_url = $url;
$current_plugin->custom_name = $name;
$current_plugin->custom_image = $image;
$list[] = $current_plugin;
}
return $list;
}
/**
* Call plugin api
*
* @param string $slug plugin slug.
*
* @return array|mixed|object
*/
private function call_plugin_api( $slug ) {
include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );
$call_api = get_transient( 'ti_plugin_info_' . $slug );
if ( false === $call_api ) {
$call_api = plugins_api(
'plugin_information',
array(
'slug' => $slug,
'fields' => array(
'downloaded' => false,
'rating' => false,
'description' => false,
'short_description' => true,
'donate_link' => false,
'tags' => false,
'sections' => true,
'homepage' => true,
'added' => false,
'last_updated' => false,
'compatibility' => false,
'tested' => false,
'requires' => false,
'downloadlink' => false,
'icons' => true,
'banners' => true,
),
)
);
set_transient( 'ti_plugin_info_' . $slug, $call_api, 30 * MINUTE_IN_SECONDS );
}
return $call_api;
}
/**
* Load css and scripts for the plugin recommend page.
*/
public function enqueue() {
$screen = get_current_screen();
if ( ! isset( $screen->id ) ) {
return;
}
if ( false === apply_filters( $this->product->get_key() . '_enqueue_recommend', false, $screen->id ) ) {
return;
}
?>
<style type="text/css">
.recommend-product {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.recommend-product .theme-banner {
width:200px;
margin: auto;
}
.recommend-product .plugin-banner {
width: 100px;
margin: auto;
}
.recommend-product .plugin_box .button span{
margin-top: 2px;
margin-right: 7px;
}
.recommend-product .plugin_box .button{
margin-bottom:10px;
}
.recommend-product .plugin_box {
margin-bottom: 20px;
padding-top: 5px;
display: flex;
box-shadow: 0px 0px 10px -5px rgba(0,0,0,0.55);
background: #fff;
border-radius: 5px;
flex-direction: column;
justify-content: flex-start;
width: 95%;
}
.recommend-product .title-action-wrapper {
padding: 15px 20px 5px 20px;
}
.recommend-product .plugin-name {
font-size: 18px;
display: block;
white-space: nowrap;
text-overflow: ellipsis;
margin-bottom: 10px;
overflow: hidden;
line-height: normal;
}
.recommend-product .plugin-desc {
display: block;
margin-bottom: 10px;
font-size: 13px;
color: #777;
line-height: 1.6;
}
.recommend-product .button-wrap > div {
padding: 0;
margin: 0;
}
.plugin-box-footer {
display: flex;
justify-content: space-around;
vertical-align: middle;
align-items: center;
padding: 0px 10px 5px;
flex: 1;
margin-top: auto;
}
</style>
<?php
}
}

View File

@ -0,0 +1,117 @@
<?php
/**
* The Review model class for ThemeIsle SDK
*
* @package ThemeIsleSDK
* @subpackage Modules
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Product;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Review module for ThemeIsle SDK.
*/
class Review extends Abstract_Module {
/**
* Check if we should load module for this.
*
* @param Product $product Product to check.
*
* @return bool Should load ?
*/
public function can_load( $product ) {
if ( $this->is_from_partner( $product ) ) {
return false;
}
if ( ! $product->is_wordpress_available() ) {
return false;
}
return apply_filters( $product->get_slug() . '_sdk_should_review', true );
}
/**
* Add notification to queue.
*
* @param array $all_notifications Previous notification.
*
* @return array All notifications.
*/
public function add_notification( $all_notifications ) {
$developers = [
'Bogdan',
'Marius',
'Hardeep',
'Rodica',
'Stefan',
'Uriahs',
'Madalin',
'Radu',
'Silviu',
'Andrei',
];
$link = 'https://wordpress.org/support/' . $this->product->get_type() . '/' . $this->product->get_slug() . '/reviews/#wporg-footer';
$message = apply_filters( $this->product->get_key() . '_feedback_review_message', '<p>Hey, its great to see you have <b>{product}</b> active for a few days now. How is everything going? If you can spare a few moments to rate it on WordPress.org it would help us a lot (and boost my motivation). Cheers! <br/> <br/>~ {developer}, developer of {product}</p>' );
$button_submit = apply_filters( $this->product->get_key() . '_feedback_review_button_do', 'Ok, I will gladly help.' );
$button_cancel = apply_filters( $this->product->get_key() . '_feedback_review_button_cancel', 'No, thanks.' );
$message = str_replace(
[ '{product}', '{developer}' ],
[
$this->product->get_friendly_name(),
$developers[ strlen( get_site_url() ) % 10 ],
],
$message
);
$all_notifications[] = [
'id' => $this->product->get_key() . '_review_flag',
'message' => $message,
'ctas' => [
'confirm' => [
'link' => $link,
'text' => $button_submit,
],
'cancel' => [
'link' => '#',
'text' => $button_cancel,
],
],
];
return $all_notifications;
}
/**
* Load module logic.
*
* @param Product $product Product to load.
*
* @return Review Module instance.
*/
public function load( $product ) {
$this->product = $product;
add_filter( 'themeisle_sdk_registered_notifications', [ $this, 'add_notification' ] );
return $this;
}
}

View File

@ -0,0 +1,376 @@
<?php
/**
* The rollback class for ThemeIsle SDK.
*
* @package ThemeIsleSDK
* @subpackage Rollback
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
// Exit if accessed directly.
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Product;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Rollback for ThemeIsle SDK.
*/
class Rollback extends Abstract_Module {
/**
* Add js scripts for themes rollback.
*/
public function add_footer() {
$screen = get_current_screen();
if ( ! isset( $screen->parent_file ) ) {
return;
}
if ( 'themes.php' !== $screen->parent_file ) {
return;
}
if ( ! $this->product->is_theme() ) {
return;
}
$version = $this->get_rollback();
if ( empty( $version ) ) {
return;
}
?>
<script type="text/javascript">
jQuery(document).ready(function ($) {
setInterval(checkTheme, 500);
function checkTheme() {
var theme = '<?php echo esc_attr( $this->product->get_slug() ); ?>-action';
if (jQuery('#' + theme).length > 0) {
if (jQuery('.theme-overlay.active').is(':visible')) {
if (jQuery('#' + theme + '-rollback').length === 0) {
jQuery('.theme-actions .active-theme').prepend('<a class="button" style="float:left" id="' + theme + '-rollback" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=' . $this->product->get_key() . '_rollback' ), $this->product->get_key() . '_rollback' ) ); ?>">Rollback to v<?php echo esc_attr( $version['version'] ); ?></a>')
}
}
}
}
})
</script>
<?php
}
/**
* Get the last rollback for this product.
*
* @return array The rollback version.
*/
public function get_rollback() {
$rollback = array();
$versions = $this->get_api_versions();
$versions = apply_filters( $this->product->get_key() . '_rollbacks', $versions );
if ( empty( $versions ) ) {
return $rollback;
}
if ( $versions ) {
usort( $versions, array( $this, 'sort_rollback_array' ) );
foreach ( $versions as $version ) {
if ( isset( $version['version'] ) && isset( $version['url'] ) && version_compare( $this->product->get_version(), $version['version'], '>' ) ) {
$rollback = $version;
break;
}
}
}
return $rollback;
}
/**
* Get versions array from wp.org
*
* @return array Array of versions.
*/
private function get_api_versions() {
$cache_key = $this->product->get_key() . '_' . preg_replace( '/[^0-9a-zA-Z ]/m', '', $this->product->get_version() ) . 'versions';
$cache_versions = get_transient( $cache_key );
if ( false === $cache_versions ) {
$versions = $this->get_remote_versions();
set_transient( $cache_key, $versions, 5 * DAY_IN_SECONDS );
} else {
$versions = is_array( $cache_versions ) ? $cache_versions : array();
}
return $versions;
}
/**
* Get remote versions zips.
*
* @return array Array of available versions.
*/
private function get_remote_versions() {
$url = $this->get_versions_api_url();
if ( empty( $url ) ) {
return [];
}
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) ) {
return array();
}
$response = wp_remote_retrieve_body( $response );
if ( is_serialized( $response ) ) {
$response = maybe_unserialize( $response );
} else {
$response = json_decode( $response );
}
if ( ! is_object( $response ) ) {
return array();
}
if ( ! isset( $response->versions ) ) {
return array();
}
$versions = array();
foreach ( $response->versions as $key => $value ) {
$versions[] = array(
'version' => is_object( $value ) ? $value->version : $key,
'url' => is_object( $value ) ? $value->file : $value,
);
}
return $versions;
}
/**
* Return url where to check for versions.
*
* @return string Url where to check for versions.
*/
private function get_versions_api_url() {
if ( $this->product->is_wordpress_available() && $this->product->is_plugin() ) {
return sprintf( 'https://api.wordpress.org/plugins/info/1.0/%s', $this->product->get_slug() );
}
if ( $this->product->is_wordpress_available() && $this->product->is_theme() ) {
return sprintf( 'https://api.wordpress.org/themes/info/1.1/?action=theme_information&request[slug]=%s&request[fields][versions]=true', $this->product->get_slug() );
}
$license = $this->product->get_license();
if ( $this->product->requires_license() && strlen( $license ) < 10 ) {
return '';
}
return sprintf( '%s?edd_action=get_versions&name=%s&url=%s&license=%s', $this->product->get_store_url(), urlencode( $this->product->get_name() ), urlencode( get_site_url() ), $license );
}
/**
* Show the rollback links in the plugin page.
*
* @param array $links Plugin links.
*
* @return array $links Altered links.
*/
public function add_rollback_link( $links ) {
$version = $this->get_rollback();
if ( empty( $version ) ) {
return $links;
}
$links[] = '<a href="' . wp_nonce_url( admin_url( 'admin-post.php?action=' . $this->product->get_key() . '_rollback' ), $this->product->get_key() . '_rollback' ) . '">' . sprintf( apply_filters( $this->product->get_key() . '_rollback_label', 'Rollback to v%s' ), $version['version'] ) . '</a>';
return $links;
}
/**
* Start the rollback operation.
*/
public function start_rollback() {
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], $this->product->get_key() . '_rollback' ) ) {
wp_nonce_ays( '' );
}
if ( $this->product->is_plugin() ) {
$this->start_rollback_plugin();
return;
}
if ( $this->product->is_theme() ) {
$this->start_rollback_theme();
return;
}
}
/**
* Start the rollback operation for the plugin.
*/
private function start_rollback_plugin() {
$rollback = $this->get_rollback();
$plugin_transient = get_site_transient( 'update_plugins' );
$plugin_folder = $this->product->get_slug();
$plugin_file = $this->product->get_file();
$version = $rollback['version'];
$temp_array = array(
'slug' => $plugin_folder,
'new_version' => $version,
'package' => $rollback['url'],
);
$temp_object = (object) $temp_array;
$plugin_transient->response[ $plugin_folder . '/' . $plugin_file ] = $temp_object;
set_site_transient( 'update_plugins', $plugin_transient );
$transient = get_transient( $this->product->get_key() . '_warning_rollback' );
if ( false === $transient ) {
set_transient( $this->product->get_key() . '_warning_rollback', 'in progress', 30 );
require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
$title = sprintf( apply_filters( $this->product->get_key() . '_rollback_message', 'Rolling back %s to v%s' ), $this->product->get_name(), $version );
$plugin = $plugin_folder . '/' . $plugin_file;
$nonce = 'upgrade-plugin_' . $plugin;
$url = 'update.php?action=upgrade-plugin&plugin=' . urlencode( $plugin );
$upgrader_skin = new \Plugin_Upgrader_Skin( compact( 'title', 'nonce', 'url', 'plugin' ) );
$upgrader = new \Plugin_Upgrader( $upgrader_skin );
$upgrader->upgrade( $plugin );
delete_transient( $this->product->get_key() . '_warning_rollback' );
wp_die(
'',
$title,
array(
'response' => 200,
)
);
}
}
/**
* Start the rollback operation for the theme.
*/
private function start_rollback_theme() {
add_filter( 'update_theme_complete_actions', array( $this, 'alter_links_theme_upgrade' ) );
$rollback = $this->get_rollback();
$transient = get_site_transient( 'update_themes' );
$folder = $this->product->get_slug();
$version = $rollback['version'];
$temp_array = array(
'new_version' => $version,
'package' => $rollback['url'],
);
$transient->response[ $folder . '/style.css' ] = $temp_array;
set_site_transient( 'update_themes', $transient );
$transient = get_transient( $this->product->get_key() . '_warning_rollback' );
if ( false === $transient ) {
set_transient( $this->product->get_key() . '_warning_rollback', 'in progress', 30 );
require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
$title = sprintf( apply_filters( $this->product->get_key() . '_rollback_message', 'Rolling back %s to v%s' ), $this->product->get_name(), $version );
$theme = $folder . '/style.css';
$nonce = 'upgrade-theme_' . $theme;
$url = 'update.php?action=upgrade-theme&theme=' . urlencode( $theme );
$upgrader = new \Theme_Upgrader( new \Theme_Upgrader_Skin( compact( 'title', 'nonce', 'url', 'theme' ) ) );
$upgrader->upgrade( $theme );
delete_transient( $this->product->get_key() . '_warning_rollback' );
wp_die(
'',
$title,
array(
'response' => 200,
)
);
}
}
/**
* Alter links and remove duplicate customize message.
*
* @param array $links Array of old links.
*
* @return mixed Array of links.
*/
public function alter_links_theme_upgrade( $links ) {
if ( isset( $links['preview'] ) ) {
$links['preview'] = str_replace( '<span aria-hidden="true">Customize</span>', '', $links['preview'] );
}
return $links;
}
/**
* Loads product object.
*
* @param Product $product Product object.
*
* @return bool Should we load the module?
*/
public function can_load( $product ) {
if ( $this->is_from_partner( $product ) ) {
return false;
}
if ( $product->is_theme() && ! current_user_can( 'switch_themes' ) ) {
return false;
}
if ( $product->is_plugin() && ! current_user_can( 'install_plugins' ) ) {
return false;
}
return true;
}
/**
* Sort the rollbacks array in descending order.
*
* @param mixed $a First version to compare.
* @param mixed $b Second version to compare.
*
* @return bool Which version is greater?
*/
public function sort_rollback_array( $a, $b ) {
return version_compare( $a['version'], $b['version'], '<' ) > 0;
}
/**
* Load module logic.
*
* @param Product $product Product object.
*
* @return $this Module object.
*/
public function load( $product ) {
$this->product = $product;
$this->show_link();
$this->add_hooks();
return $this;
}
/**
* If product can be rolled back, show the link to rollback.
*/
private function show_link() {
add_filter(
'plugin_action_links_' . plugin_basename( $this->product->get_basefile() ),
array(
$this,
'add_rollback_link',
)
);
}
/**
* Set the rollback hook. Strangely, this does not work if placed in the ThemeIsle_SDK_Rollback class, so it is being called from there instead.
*/
public function add_hooks() {
add_action( 'admin_post_' . $this->product->get_key() . '_rollback', array( $this, 'start_rollback' ) );
add_action( 'admin_footer', array( $this, 'add_footer' ) );
}
}

View File

@ -0,0 +1,918 @@
<?php
/**
* The translate model class for ThemeIsle SDK
*
* @package ThemeIsleSDK
* @subpackage Modules
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Product;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Translate module for ThemeIsle SDK.
*/
class Translate extends Abstract_Module {
/**
* List of available locales.
*
* @var array Array of available locals.
*/
private static $locales = array(
'af' => array(
'slug' => 'af',
'name' => 'Afrikaans',
),
'ak' => array(
'slug' => 'ak',
'name' => 'Akan',
),
'am' => array(
'slug' => 'am',
'name' => 'Amharic',
),
'ar' => array(
'slug' => 'ar',
'name' => 'Arabic',
),
'arq' => array(
'slug' => 'arq',
'name' => 'Algerian Arabic',
),
'ary' => array(
'slug' => 'ary',
'name' => 'Moroccan Arabic',
),
'as' => array(
'slug' => 'as',
'name' => 'Assamese',
),
'ast' => array(
'slug' => 'ast',
'name' => 'Asturian',
),
'az' => array(
'slug' => 'az',
'name' => 'Azerbaijani',
),
'azb' => array(
'slug' => 'azb',
'name' => 'South Azerbaijani',
),
'az_TR' => array(
'slug' => 'az-tr',
'name' => 'Azerbaijani (Turkey)',
),
'ba' => array(
'slug' => 'ba',
'name' => 'Bashkir',
),
'bal' => array(
'slug' => 'bal',
'name' => 'Catalan (Balear)',
),
'bcc' => array(
'slug' => 'bcc',
'name' => 'Balochi Southern',
),
'bel' => array(
'slug' => 'bel',
'name' => 'Belarusian',
),
'bg_BG' => array(
'slug' => 'bg',
'name' => 'Bulgarian',
),
'bn_BD' => array(
'slug' => 'bn',
'name' => 'Bengali',
),
'bo' => array(
'slug' => 'bo',
'name' => 'Tibetan',
),
'bre' => array(
'slug' => 'br',
'name' => 'Breton',
),
'bs_BA' => array(
'slug' => 'bs',
'name' => 'Bosnian',
),
'ca' => array(
'slug' => 'ca',
'name' => 'Catalan',
),
'ceb' => array(
'slug' => 'ceb',
'name' => 'Cebuano',
),
'ckb' => array(
'slug' => 'ckb',
'name' => 'Kurdish (Sorani)',
),
'co' => array(
'slug' => 'co',
'name' => 'Corsican',
),
'cs_CZ' => array(
'slug' => 'cs',
'name' => 'Czech',
),
'cy' => array(
'slug' => 'cy',
'name' => 'Welsh',
),
'da_DK' => array(
'slug' => 'da',
'name' => 'Danish',
),
'de_DE' => array(
'slug' => 'de',
'name' => 'German',
),
'de_CH' => array(
'slug' => 'de-ch',
'name' => 'German (Switzerland)',
),
'dv' => array(
'slug' => 'dv',
'name' => 'Dhivehi',
),
'dzo' => array(
'slug' => 'dzo',
'name' => 'Dzongkha',
),
'el' => array(
'slug' => 'el',
'name' => 'Greek',
),
'art_xemoji' => array(
'slug' => 'art-xemoji',
'name' => 'Emoji',
),
'en_US' => array(
'slug' => 'en',
'name' => 'English',
),
'en_AU' => array(
'slug' => 'en-au',
'name' => 'English (Australia)',
),
'en_CA' => array(
'slug' => 'en-ca',
'name' => 'English (Canada)',
),
'en_GB' => array(
'slug' => 'en-gb',
'name' => 'English (UK)',
),
'en_NZ' => array(
'slug' => 'en-nz',
'name' => 'English (New Zealand)',
),
'en_ZA' => array(
'slug' => 'en-za',
'name' => 'English (South Africa)',
),
'eo' => array(
'slug' => 'eo',
'name' => 'Esperanto',
),
'es_ES' => array(
'slug' => 'es',
'name' => 'Spanish (Spain)',
),
'es_AR' => array(
'slug' => 'es-ar',
'name' => 'Spanish (Argentina)',
),
'es_CL' => array(
'slug' => 'es-cl',
'name' => 'Spanish (Chile)',
),
'es_CO' => array(
'slug' => 'es-co',
'name' => 'Spanish (Colombia)',
),
'es_CR' => array(
'slug' => 'es-cr',
'name' => 'Spanish (Costa Rica)',
),
'es_GT' => array(
'slug' => 'es-gt',
'name' => 'Spanish (Guatemala)',
),
'es_MX' => array(
'slug' => 'es-mx',
'name' => 'Spanish (Mexico)',
),
'es_PE' => array(
'slug' => 'es-pe',
'name' => 'Spanish (Peru)',
),
'es_PR' => array(
'slug' => 'es-pr',
'name' => 'Spanish (Puerto Rico)',
),
'es_VE' => array(
'slug' => 'es-ve',
'name' => 'Spanish (Venezuela)',
),
'et' => array(
'slug' => 'et',
'name' => 'Estonian',
),
'eu' => array(
'slug' => 'eu',
'name' => 'Basque',
),
'fa_IR' => array(
'slug' => 'fa',
'name' => 'Persian',
),
'fa_AF' => array(
'slug' => 'fa-af',
'name' => 'Persian (Afghanistan)',
),
'fuc' => array(
'slug' => 'fuc',
'name' => 'Fulah',
),
'fi' => array(
'slug' => 'fi',
'name' => 'Finnish',
),
'fo' => array(
'slug' => 'fo',
'name' => 'Faroese',
),
'fr_FR' => array(
'slug' => 'fr',
'name' => 'French (France)',
),
'fr_BE' => array(
'slug' => 'fr-be',
'name' => 'French (Belgium)',
),
'fr_CA' => array(
'slug' => 'fr-ca',
'name' => 'French (Canada)',
),
'frp' => array(
'slug' => 'frp',
'name' => 'Arpitan',
),
'fur' => array(
'slug' => 'fur',
'name' => 'Friulian',
),
'fy' => array(
'slug' => 'fy',
'name' => 'Frisian',
),
'ga' => array(
'slug' => 'ga',
'name' => 'Irish',
),
'gd' => array(
'slug' => 'gd',
'name' => 'Scottish Gaelic',
),
'gl_ES' => array(
'slug' => 'gl',
'name' => 'Galician',
),
'gn' => array(
'slug' => 'gn',
'name' => 'Guaraní',
),
'gsw' => array(
'slug' => 'gsw',
'name' => 'Swiss German',
),
'gu' => array(
'slug' => 'gu',
'name' => 'Gujarati',
),
'hat' => array(
'slug' => 'hat',
'name' => 'Haitian Creole',
),
'hau' => array(
'slug' => 'hau',
'name' => 'Hausa',
),
'haw_US' => array(
'slug' => 'haw',
'name' => 'Hawaiian',
),
'haz' => array(
'slug' => 'haz',
'name' => 'Hazaragi',
),
'he_IL' => array(
'slug' => 'he',
'name' => 'Hebrew',
),
'hi_IN' => array(
'slug' => 'hi',
'name' => 'Hindi',
),
'hr' => array(
'slug' => 'hr',
'name' => 'Croatian',
),
'hu_HU' => array(
'slug' => 'hu',
'name' => 'Hungarian',
),
'hy' => array(
'slug' => 'hy',
'name' => 'Armenian',
),
'id_ID' => array(
'slug' => 'id',
'name' => 'Indonesian',
),
'ido' => array(
'slug' => 'ido',
'name' => 'Ido',
),
'is_IS' => array(
'slug' => 'is',
'name' => 'Icelandic',
),
'it_IT' => array(
'slug' => 'it',
'name' => 'Italian',
),
'ja' => array(
'slug' => 'ja',
'name' => 'Japanese',
),
'jv_ID' => array(
'slug' => 'jv',
'name' => 'Javanese',
),
'ka_GE' => array(
'slug' => 'ka',
'name' => 'Georgian',
),
'kab' => array(
'slug' => 'kab',
'name' => 'Kabyle',
),
'kal' => array(
'slug' => 'kal',
'name' => 'Greenlandic',
),
'kin' => array(
'slug' => 'kin',
'name' => 'Kinyarwanda',
),
'kk' => array(
'slug' => 'kk',
'name' => 'Kazakh',
),
'km' => array(
'slug' => 'km',
'name' => 'Khmer',
),
'kn' => array(
'slug' => 'kn',
'name' => 'Kannada',
),
'ko_KR' => array(
'slug' => 'ko',
'name' => 'Korean',
),
'kir' => array(
'slug' => 'kir',
'name' => 'Kyrgyz',
),
'lb_LU' => array(
'slug' => 'lb',
'name' => 'Luxembourgish',
),
'li' => array(
'slug' => 'li',
'name' => 'Limburgish',
),
'lin' => array(
'slug' => 'lin',
'name' => 'Lingala',
),
'lo' => array(
'slug' => 'lo',
'name' => 'Lao',
),
'lt_LT' => array(
'slug' => 'lt',
'name' => 'Lithuanian',
),
'lv' => array(
'slug' => 'lv',
'name' => 'Latvian',
),
'me_ME' => array(
'slug' => 'me',
'name' => 'Montenegrin',
),
'mg_MG' => array(
'slug' => 'mg',
'name' => 'Malagasy',
),
'mk_MK' => array(
'slug' => 'mk',
'name' => 'Macedonian',
),
'ml_IN' => array(
'slug' => 'ml',
'name' => 'Malayalam',
),
'mlt' => array(
'slug' => 'mlt',
'name' => 'Maltese',
),
'mn' => array(
'slug' => 'mn',
'name' => 'Mongolian',
),
'mr' => array(
'slug' => 'mr',
'name' => 'Marathi',
),
'mri' => array(
'slug' => 'mri',
'name' => 'Maori',
),
'ms_MY' => array(
'slug' => 'ms',
'name' => 'Malay',
),
'my_MM' => array(
'slug' => 'mya',
'name' => 'Myanmar (Burmese)',
),
'ne_NP' => array(
'slug' => 'ne',
'name' => 'Nepali',
),
'nb_NO' => array(
'slug' => 'nb',
'name' => 'Norwegian (Bokmål)',
),
'nl_NL' => array(
'slug' => 'nl',
'name' => 'Dutch',
),
'nl_BE' => array(
'slug' => 'nl-be',
'name' => 'Dutch (Belgium)',
),
'nn_NO' => array(
'slug' => 'nn',
'name' => 'Norwegian (Nynorsk)',
),
'oci' => array(
'slug' => 'oci',
'name' => 'Occitan',
),
'ory' => array(
'slug' => 'ory',
'name' => 'Oriya',
),
'os' => array(
'slug' => 'os',
'name' => 'Ossetic',
),
'pa_IN' => array(
'slug' => 'pa',
'name' => 'Punjabi',
),
'pl_PL' => array(
'slug' => 'pl',
'name' => 'Polish',
),
'pt_BR' => array(
'slug' => 'pt-br',
'name' => 'Portuguese (Brazil)',
),
'pt_PT' => array(
'slug' => 'pt',
'name' => 'Portuguese (Portugal)',
),
'ps' => array(
'slug' => 'ps',
'name' => 'Pashto',
),
'rhg' => array(
'slug' => 'rhg',
'name' => 'Rohingya',
),
'ro_RO' => array(
'slug' => 'ro',
'name' => 'Romanian',
),
'roh' => array(
'slug' => 'roh',
'name' => 'Romansh',
),
'ru_RU' => array(
'slug' => 'ru',
'name' => 'Russian',
),
'rue' => array(
'slug' => 'rue',
'name' => 'Rusyn',
),
'rup_MK' => array(
'slug' => 'rup',
'name' => 'Aromanian',
),
'sah' => array(
'slug' => 'sah',
'name' => 'Sakha',
),
'sa_IN' => array(
'slug' => 'sa-in',
'name' => 'Sanskrit',
),
'scn' => array(
'slug' => 'scn',
'name' => 'Sicilian',
),
'si_LK' => array(
'slug' => 'si',
'name' => 'Sinhala',
),
'sk_SK' => array(
'slug' => 'sk',
'name' => 'Slovak',
),
'sl_SI' => array(
'slug' => 'sl',
'name' => 'Slovenian',
),
'sna' => array(
'slug' => 'sna',
'name' => 'Shona',
),
'snd' => array(
'slug' => 'snd',
'name' => 'Sindhi',
),
'so_SO' => array(
'slug' => 'so',
'name' => 'Somali',
),
'sq' => array(
'slug' => 'sq',
'name' => 'Albanian',
),
'sq_XK' => array(
'slug' => 'sq-xk',
'name' => 'Shqip (Kosovo)',
),
'sr_RS' => array(
'slug' => 'sr',
'name' => 'Serbian',
),
'srd' => array(
'slug' => 'srd',
'name' => 'Sardinian',
),
'su_ID' => array(
'slug' => 'su',
'name' => 'Sundanese',
),
'sv_SE' => array(
'slug' => 'sv',
'name' => 'Swedish',
),
'sw' => array(
'slug' => 'sw',
'name' => 'Swahili',
),
'syr' => array(
'slug' => 'syr',
'name' => 'Syriac',
),
'szl' => array(
'slug' => 'szl',
'name' => 'Silesian',
),
'ta_IN' => array(
'slug' => 'ta',
'name' => 'Tamil',
),
'ta_LK' => array(
'slug' => 'ta-lk',
'name' => 'Tamil (Sri Lanka)',
),
'tah' => array(
'slug' => 'tah',
'name' => 'Tahitian',
),
'te' => array(
'slug' => 'te',
'name' => 'Telugu',
),
'tg' => array(
'slug' => 'tg',
'name' => 'Tajik',
),
'th' => array(
'slug' => 'th',
'name' => 'Thai',
),
'tir' => array(
'slug' => 'tir',
'name' => 'Tigrinya',
),
'tl' => array(
'slug' => 'tl',
'name' => 'Tagalog',
),
'tr_TR' => array(
'slug' => 'tr',
'name' => 'Turkish',
),
'tt_RU' => array(
'slug' => 'tt',
'name' => 'Tatar',
),
'tuk' => array(
'slug' => 'tuk',
'name' => 'Turkmen',
),
'twd' => array(
'slug' => 'twd',
'name' => 'Tweants',
),
'tzm' => array(
'slug' => 'tzm',
'name' => 'Tamazight (Central Atlas)',
),
'ug_CN' => array(
'slug' => 'ug',
'name' => 'Uighur',
),
'uk' => array(
'slug' => 'uk',
'name' => 'Ukrainian',
),
'ur' => array(
'slug' => 'ur',
'name' => 'Urdu',
),
'uz_UZ' => array(
'slug' => 'uz',
'name' => 'Uzbek',
),
'vi' => array(
'slug' => 'vi',
'name' => 'Vietnamese',
),
'wa' => array(
'slug' => 'wa',
'name' => 'Walloon',
),
'xho' => array(
'slug' => 'xho',
'name' => 'Xhosa',
),
'xmf' => array(
'slug' => 'xmf',
'name' => 'Mingrelian',
),
'yor' => array(
'slug' => 'yor',
'name' => 'Yoruba',
),
'zh_CN' => array(
'slug' => 'zh-cn',
'name' => 'Chinese (China)',
),
'zh_HK' => array(
'slug' => 'zh-hk',
'name' => 'Chinese (Hong Kong)',
),
'zh_TW' => array(
'slug' => 'zh-tw',
'name' => 'Chinese (Taiwan)',
),
'de_DE_formal' => array(
'slug' => 'de/formal',
'name' => 'German (Formal)',
),
'nl_NL_formal' => array(
'slug' => 'nl/formal',
'name' => 'Dutch (Formal)',
),
'de_CH_informal' => array(
'slug' => 'de-ch/informal',
'name' => 'Chinese (Taiwan)',
),
'pt_PT_ao90' => array(
'slug' => 'pt/ao90',
'name' => 'Portuguese (Portugal, AO90)',
),
);
/**
* Check if we should load module for this.
*
* @param Product $product Product to check.
*
* @return bool Should load ?
*/
public function can_load( $product ) {
if ( $this->is_from_partner( $product ) ) {
return false;
}
if ( ! $product->is_wordpress_available() ) {
return false;
}
$lang = $this->get_user_locale();
if ( 'en_US' === $lang ) {
return false;
}
$languages = $this->get_translations( $product );
if ( ! is_array( $languages ) ) {
return false;
}
if ( ! isset( $languages['translations'] ) ) {
return false;
}
$languages = $languages['translations'];
$available = wp_list_pluck( $languages, 'language' );
if ( in_array( $lang, $available ) ) {
return false;
}
if ( ! isset( self::$locales[ $lang ] ) ) {
return false;
}
return apply_filters( $product->get_slug() . '_sdk_enable_translate', true );
}
/**
* Get the user's locale.
*/
private function get_user_locale() {
global $wp_version;
if ( version_compare( $wp_version, '4.7.0', '>=' ) ) {
return get_user_locale();
}
$user = wp_get_current_user();
if ( $user ) {
$locale = $user->locale;
}
return $locale ? $locale : get_locale();
}
/**
* Fetch translations from api.
*
* @param Product $product Product to check.
*
* @return mixed Translation array.
*/
private function get_translations( $product ) {
$cache_key = $product->get_key() . '_all_languages';
$translations = get_transient( $cache_key );
if ( false === $translations ) {
require_once( ABSPATH . 'wp-admin/includes/translation-install.php' );
$translations = translations_api(
$product->get_type() . 's',
array(
'slug' => $product->get_slug(),
'version' => $product->get_version(),
)
);
set_transient( $cache_key, $translations, WEEK_IN_SECONDS );
}
return $translations;
}
/**
* Add notification to queue.
*
* @param array $all_notifications Previous notification.
*
* @return array All notifications.
*/
public function add_notification( $all_notifications ) {
$lang = $this->get_user_locale();
$link = $this->get_locale_paths( $lang );
$language_meta = self::$locales[ $lang ];
$heading = apply_filters( $this->product->get_key() . '_feedback_translate_heading', 'Improve {product}' );
$heading = str_replace(
array( '{product}' ),
$this->product->get_friendly_name(),
$heading
);
$message = apply_filters(
$this->product->get_key() . '_feedback_translation',
'Translating <b>{product}</b> into as many languages as possible is a huge project. We still need help with a lot of them, so if you are good at translating into <b>{language}</b>, it would be greatly appreciated.
The process is easy, and you can join by following the link below!'
);
$message = str_replace(
[ '{product}', '{language}' ],
[
$this->product->get_friendly_name(),
$language_meta['name'],
],
$message
);
$button_submit = apply_filters( $this->product->get_key() . '_feedback_translate_button_do', 'Ok, I will gladly help.' );
$button_cancel = apply_filters( $this->product->get_key() . '_feedback_translate_button_cancel', 'No, thanks.' );
$all_notifications[] = [
'id' => $this->product->get_key() . '_translate_flag',
'heading' => $heading,
'message' => $message,
'ctas' => [
'confirm' => [
'link' => $link,
'text' => $button_submit,
],
'cancel' => [
'link' => '#',
'text' => $button_cancel,
],
],
];
return $all_notifications;
}
/**
* Return the locale path.
*
* @param string $locale Locale code.
*
* @return string Locale path.
*/
private function get_locale_paths( $locale ) {
if ( empty( $locale ) ) {
return '';
}
$slug = isset( self::$locales[ $locale ] ) ? self::$locales[ $locale ]['slug'] : '';
if ( empty( $slug ) ) {
return '';
}
if ( strpos( $slug, '/' ) === false ) {
$slug .= '/default';
}
$url = 'https://translate.wordpress.org/projects/wp-' . $this->product->get_type() . 's/' . $this->product->get_slug() . '/' . ( $this->product->get_type() === 'plugin' ? 'dev/' : '' ) . $slug . '?filters%5Bstatus%5D=untranslated&sort%5Bby%5D=random';
return $url;
}
/**
* Load module logic.
*
* @param Product $product Product to load.
*
* @return Translate Module instance.
*/
public function load( $product ) {
$this->product = $product;
add_filter( 'themeisle_sdk_registered_notifications', [ $this, 'add_notification' ] );
return $this;
}
}

View File

@ -0,0 +1,850 @@
<?php
/**
* The deactivate feedback model class for ThemeIsle SDK
*
* @package ThemeIsleSDK
* @subpackage Feedback
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK\Modules;
use ThemeisleSDK\Common\Abstract_Module;
use ThemeisleSDK\Product;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Uninstall feedback module for ThemeIsle SDK.
*/
class Uninstall_Feedback extends Abstract_Module {
/**
* How many seconds before the deactivation window is triggered for themes?
*
* @var int Number of days.
*/
const AUTO_TRIGGER_DEACTIVATE_WINDOW_SECONDS = 3;
/**
* How many days before the deactivation window pops up again for the theme?
*
* @var int Number of days.
*/
const PAUSE_DEACTIVATE_WINDOW_DAYS = 100;
/**
* Where to send the data.
*
* @var string Endpoint url.
*/
const FEEDBACK_ENDPOINT = 'http://feedback.themeisle.com/wordpress/wp-json/__pirate_feedback_/v1/feedback';
/**
* Default options for plugins.
*
* @var array $options_plugin The main options list for plugins.
*/
private $options_plugin = array(
'I found a better plugin' => array(
'id' => 3,
'type' => 'text',
'placeholder' => 'What\'s the plugin\'s name?',
),
'I could not get the plugin to work' => array(
'type' => 'textarea',
'placeholder' => 'What problem are you experiencing?',
'id' => 4,
),
'I no longer need the plugin' => array(
'id' => 5,
'type' => 'textarea',
'placeholder' => 'If you could improve one thing about our product, what would it be?',
),
'It\'s a temporary deactivation. I\'m just debugging an issue.' => array(
'type' => 'textarea',
'placeholder' => 'What problem are you experiencing?',
'id' => 6,
),
);
/**
* Default options for theme.
*
* @var array $options_theme The main options list for themes.
*/
private $options_theme = array(
'I don\'t know how to make it look like demo' => array(
'id' => 7,
),
'It lacks options' => array(
'placeholder' => 'What option is missing?',
'type' => 'text',
'id' => 8,
),
'Is not working with a plugin that I need' => array(
'id' => 9,
'type' => 'text',
'placeholder' => 'What is the name of the plugin',
),
'I want to try a new design, I don\'t like {theme} style' => array(
'id' => 10,
),
);
/**
* Default other option.
*
* @var array $other The other option
*/
private $other = array(
'Other' => array(
'id' => 999,
'type' => 'textarea',
'placeholder' => 'What can we do better?',
),
);
/**
* Default heading for plugin.
*
* @var string $heading_plugin The heading of the modal
*/
private $heading_plugin = 'Whats wrong?';
/**
* Default heading for theme.
*
* @var string $heading_theme The heading of the modal
*/
private $heading_theme = 'What does not work for you in {theme}?';
/**
* Default submit button action text.
*
* @var string $button_submit The text of the deactivate button
*/
private $button_submit = 'Submit &amp; Deactivate';
/**
* Default cancel button.
*
* @var string $button_cancel The text of the cancel button
*/
private $button_cancel = 'Skip &amp; Deactivate';
/**
* Loads the additional resources
*/
function load_resources() {
$screen = get_current_screen();
if ( ! $screen || ! in_array( $screen->id, array( 'theme-install', 'plugins' ) ) ) {
return;
}
$this->add_feedback_popup_style();
if ( $this->product->get_type() === 'theme' ) {
$this->add_theme_feedback_drawer_js();
$this->render_theme_feedback_popup();
return;
}
$this->add_plugin_feedback_popup_js();
$this->render_plugin_feedback_popup();
}
/**
* Render theme feedback drawer.
*/
private function render_theme_feedback_popup() {
$heading = str_replace( '{theme}', $this->product->get_name(), $this->heading_theme );
$button_submit = apply_filters( $this->product->get_key() . '_feedback_deactivate_button_submit', 'Submit' );
$options = $this->options_theme;
$options = $this->randomize_options( apply_filters( $this->product->get_key() . '_feedback_deactivate_options', $options ) );
$info_disclosure_link = '<a href="#" class="info-disclosure-link">' . apply_filters( $this->product->get_slug() . '_themeisle_sdk_info_collect_cta', 'What info do we collect?' ) . '</a>';
$options += $this->other;
?>
<div class="ti-theme-uninstall-feedback-drawer ti-feedback">
<div class="popup--header">
<h5><?php echo wp_kses( $heading, array( 'span' => true ) ); ?> </h5>
<button class="toggle"><span>&times;</span></button>
</div><!--/.popup--header-->
<div class="popup--body">
<?php $this->render_options_list( $options ); ?>
</div><!--/.popup--body-->
<div class="popup--footer">
<div class="actions">
<?php
echo wp_kses_post( $info_disclosure_link );
echo wp_kses_post( $this->get_disclosure_labels() );
echo '<div class="buttons">';
echo get_submit_button(
$button_submit,
'secondary',
$this->product->get_key() . 'ti-deactivate-yes',
false,
array(
'data-after-text' => $button_submit,
'disabled' => true,
)
);
echo '</div>';
?>
</div><!--/.actions-->
</div><!--/.popup--footer-->
</div>
<?php
}
/**
* Add feedback styles.
*/
private function add_feedback_popup_style() {
?>
<style>
.ti-feedback {
background: #fff;
max-width: 400px;
z-index: 10000;
box-shadow: 0 0 15px -5px rgba(0, 0, 0, .5);
transition: all .3s ease-out;
}
.ti-feedback .popup--header {
position: relative;
background-color: #23A1CE;
}
.ti-feedback .popup--header h5 {
margin: 0;
font-size: 16px;
padding: 15px;
color: #fff;
font-weight: 600;
text-align: center;
letter-spacing: .3px;
}
.ti-feedback .popup--body {
padding: 15px;
}
.ti-feedback .popup--form {
margin: 0;
font-size: 13px;
}
.ti-feedback .popup--form input[type="radio"] {
margin: 0 10px 0 0;
}
.ti-feedback .popup--form input[type="radio"]:checked ~ textarea {
display: block;
}
.ti-feedback .popup--form textarea {
width: 100%;
margin: 10px 0 0;
display: none;
max-height: 150px;
}
.ti-feedback li {
display: flex;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
}
.ti-feedback li label {
max-width: 90%;
}
.ti-feedback li:last-child {
margin-bottom: 0;
}
.ti-feedback .popup--footer {
padding: 0 15px 15px;
}
.ti-feedback .actions {
display: flex;
flex-wrap: wrap;
}
.info-disclosure-link {
width: 100%;
margin-bottom: 15px;
}
.ti-feedback .info-disclosure-content {
max-height: 0;
overflow: hidden;
width: 100%;
transition: .3s ease;
}
.ti-feedback .info-disclosure-content.active {
max-height: 300px;
}
.ti-feedback .info-disclosure-content p {
margin: 0;
}
.ti-feedback .info-disclosure-content ul {
margin: 10px 0;
border-radius: 3px;
}
.ti-feedback .info-disclosure-content ul li {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0;
padding: 5px 0;
border-bottom: 1px solid #ccc;
}
.ti-feedback .buttons {
display: flex;
width: 100%;
}
.ti-feedback .buttons input:last-child {
margin-left: auto;
}
.ti-theme-uninstall-feedback-drawer {
border-top-left-radius: 5px;
position: fixed;
top: 100%;
right: 15px;
}
.ti-theme-uninstall-feedback-drawer.active {
transform: translateY(-100%);
}
.ti-theme-uninstall-feedback-drawer .popup--header {
border-top-left-radius: 5px;
}
.ti-theme-uninstall-feedback-drawer .popup--header .toggle {
position: absolute;
padding: 3px 0;
width: 30px;
top: -26px;
right: 0;
cursor: pointer;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
font-size: 20px;
background-color: #23A1CE;
color: #fff;
border: none;
line-height: 20px;
}
.ti-theme-uninstall-feedback-drawer .toggle span {
margin: 0;
display: inline-block;
}
.ti-theme-uninstall-feedback-drawer:not(.active) .toggle span {
transform: rotate(45deg);
}
.ti-theme-uninstall-feedback-drawer .popup--header .toggle:hover {
background-color: #1880a5;
}
.ti-plugin-uninstall-feedback-popup .popup--header:before {
content: "";
display: block;
position: absolute;
border: 20px solid #23A1CE;
left: -10px;
top: 50%;
border-top: 20px solid transparent;
border-bottom: 20px solid transparent;
border-left: 0;
transform: translateY(-50%);
}
.ti-plugin-uninstall-feedback-popup {
display: none;
position: absolute;
white-space: normal;
width: 400px;
left: 100%;
top: -15px;
}
.ti-plugin-uninstall-feedback-popup.sending-feedback .popup--body i {
animation: rotation 2s infinite linear;
display: block;
float: none;
align-items: center;
width: 100%;
margin: 0 auto;
height: 100%;
background: transparent;
padding: 0;
}
.ti-plugin-uninstall-feedback-popup.sending-feedback .popup--body i:before {
padding: 0;
background: transparent;
box-shadow: none;
color: #b4b9be
}
.ti-plugin-uninstall-feedback-popup.active {
display: block;
}
tr[data-plugin^="<?php echo $this->product->get_slug(); ?>"] .deactivate {
position: relative;
}
body.ti-feedback-open .ti-feedback-overlay {
content: "";
display: block;
background-color: rgba(0, 0, 0, 0.5);
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 10000;
position: fixed;
}
@media (max-width: 768px) {
.ti-plugin-uninstall-feedback-popup {
position: fixed;
max-width: 100%;
margin: 0 auto;
left: 50%;
top: 50px;
transform: translateX(-50%);
}
.ti-plugin-uninstall-feedback-popup .popup--header:before {
display: none;
}
}
</style>
<?php
}
/**
* Theme feedback drawer JS.
*/
private function add_theme_feedback_drawer_js() {
$key = $this->product->get_key();
?>
<script type="text/javascript" id="ti-deactivate-js">
(function ($) {
$(document).ready(function () {
setTimeout(function () {
$('.ti-theme-uninstall-feedback-drawer').addClass('active');
}, <?php echo absint( self::AUTO_TRIGGER_DEACTIVATE_WINDOW_SECONDS * 1000 ); ?> );
$('.ti-theme-uninstall-feedback-drawer .toggle').on('click', function (e) {
e.preventDefault();
$('.ti-theme-uninstall-feedback-drawer').toggleClass('active');
});
$('.info-disclosure-link').on('click', function (e) {
e.preventDefault();
$('.info-disclosure-content').toggleClass('active');
});
$('.ti-theme-uninstall-feedback-drawer input[type="radio"]').on('change', function () {
var radio = $(this);
if (radio.parent().find('textarea').length > 0 &&
radio.parent().find('textarea').val().length === 0) {
$('#<?php echo $key; ?>ti-deactivate-yes').attr('disabled', 'disabled');
radio.parent().find('textarea').on('keyup', function (e) {
if ($(this).val().length === 0) {
$('#<?php echo $key; ?>ti-deactivate-yes').attr('disabled', 'disabled');
} else {
$('#<?php echo $key; ?>ti-deactivate-yes').removeAttr('disabled');
}
});
} else {
$('#<?php echo $key; ?>ti-deactivate-yes').removeAttr('disabled');
}
});
$('#<?php echo $key; ?>ti-deactivate-yes').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
var selectedOption = $(
'.ti-theme-uninstall-feedback-drawer input[name="ti-deactivate-option"]:checked');
$.post(ajaxurl, {
'action': '<?php echo esc_attr( $key ) . '_uninstall_feedback'; ?>',
'nonce': '<?php echo wp_create_nonce( (string) __CLASS__ ); ?>',
'id': selectedOption.parent().attr('ti-option-id'),
'msg': selectedOption.parent().find('textarea').val(),
'type': 'theme',
'key': '<?php echo esc_attr( $key ); ?>'
});
$('.ti-theme-uninstall-feedback-drawer').fadeOut();
});
});
})(jQuery);
</script>
<?php
do_action( $this->product->get_key() . '_uninstall_feedback_after_js' );
}
/**
* Render the options list.
*
* @param array $options the options for the feedback form.
*/
private function render_options_list( $options ) {
$key = $this->product->get_key();
$inputs_row_map = [
'text' => 1,
'textarea' => 2,
];
?>
<ul class="popup--form">
<?php foreach ( $options as $title => $attributes ) { ?>
<li ti-option-id="<?php echo esc_attr( $attributes['id'] ); ?>">
<input type="radio" name="ti-deactivate-option" id="<?php echo esc_attr( $key . $attributes['id'] ); ?>">
<label for="<?php echo esc_attr( $key . $attributes['id'] ); ?>">
<?php echo str_replace( '{theme}', $this->product->get_name(), $title ); ?>
</label>
<?php
if ( array_key_exists( 'type', $attributes ) ) {
$placeholder = array_key_exists( 'placeholder', $attributes ) ? $attributes['placeholder'] : '';
echo '<textarea width="100%" rows="' . $inputs_row_map[ $attributes['type'] ] . '" name="comments" placeholder="' . esc_attr( $placeholder ) . '"></textarea>';
}
?>
</li>
<?php } ?>
</ul>
<?php
}
/**
* Render plugin feedback popup.
*/
private function render_plugin_feedback_popup() {
$button_cancel = apply_filters( $this->product->get_key() . '_feedback_deactivate_button_cancel', $this->button_cancel );
$button_submit = apply_filters( $this->product->get_key() . '_feedback_deactivate_button_submit', $this->button_submit );
$options = $this->randomize_options( apply_filters( $this->product->get_key() . '_feedback_deactivate_options', $this->options_plugin ) );
$info_disclosure_link = '<a href="#" class="info-disclosure-link">' . apply_filters( $this->product->get_slug() . '_themeisle_sdk_info_collect_cta', 'What info do we collect?' ) . '</a>';
$options += $this->other;
?>
<div class="ti-plugin-uninstall-feedback-popup ti-feedback" id="<?php echo esc_attr( $this->product->get_slug() . '_uninstall_feedback_popup' ); ?>">
<div class="popup--header">
<h5><?php echo wp_kses( $this->heading_plugin, array( 'span' => true ) ); ?> </h5>
</div><!--/.popup--header-->
<div class="popup--body">
<?php $this->render_options_list( $options ); ?>
</div><!--/.popup--body-->
<div class="popup--footer">
<div class="actions">
<?php
echo wp_kses_post( $info_disclosure_link );
echo wp_kses_post( $this->get_disclosure_labels() );
echo '<div class="buttons">';
echo get_submit_button(
$button_cancel,
'secondary',
$this->product->get_key() . 'ti-deactivate-no',
false
);
echo get_submit_button(
$button_submit,
'primary',
$this->product->get_key() . 'ti-deactivate-yes',
false,
array(
'data-after-text' => $button_submit,
'disabled' => true,
)
);
echo '</div>';
?>
</div><!--/.actions-->
</div><!--/.popup--footer-->
</div>
<?php
}
/**
* Add plugin feedback popup JS
*/
private function add_plugin_feedback_popup_js() {
$popup_id = '#' . $this->product->get_slug() . '_uninstall_feedback_popup';
$key = $this->product->get_key();
?>
<script type="text/javascript" id="ti-deactivate-js">
(function ($) {
$(document).ready(function () {
var targetElement = 'tr[data-plugin^="<?php echo $this->product->get_slug(); ?>/"] span.deactivate a';
var redirectUrl = $(targetElement).attr('href');
if ($('.ti-feedback-overlay').length === 0) {
$('body').prepend('<div class="ti-feedback-overlay"></div>');
}
$('<?php echo esc_attr( $popup_id ); ?> ').appendTo($(targetElement).parent());
$(targetElement).on('click', function (e) {
e.preventDefault();
$('<?php echo esc_attr( $popup_id ); ?> ').addClass('active');
$('body').addClass('ti-feedback-open');
$('.ti-feedback-overlay').on('click', function () {
$('<?php echo esc_attr( $popup_id ); ?> ').removeClass('active');
$('body').removeClass('ti-feedback-open');
});
});
$('<?php echo esc_attr( $popup_id ); ?> .info-disclosure-link').on('click', function (e) {
e.preventDefault();
$(this).parent().find('.info-disclosure-content').toggleClass('active');
});
$('<?php echo esc_attr( $popup_id ); ?> input[type="radio"]').on('change', function () {
var radio = $(this);
if (radio.parent().find('textarea').length > 0 &&
radio.parent().find('textarea').val().length === 0) {
$('<?php echo esc_attr( $popup_id ); ?> #<?php echo $key; ?>ti-deactivate-yes').attr('disabled', 'disabled');
radio.parent().find('textarea').on('keyup', function (e) {
if ($(this).val().length === 0) {
$('<?php echo esc_attr( $popup_id ); ?> #<?php echo $key; ?>ti-deactivate-yes').attr('disabled', 'disabled');
} else {
$('<?php echo esc_attr( $popup_id ); ?> #<?php echo $key; ?>ti-deactivate-yes').removeAttr('disabled');
}
});
} else {
$('<?php echo esc_attr( $popup_id ); ?> #<?php echo $key; ?>ti-deactivate-yes').removeAttr('disabled');
}
});
$('<?php echo esc_attr( $popup_id ); ?> #<?php echo $key; ?>ti-deactivate-no').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
$(targetElement).unbind('click');
$('body').removeClass('ti-feedback-open');
$('<?php echo esc_attr( $popup_id ); ?>').remove();
if (redirectUrl !== '') {
location.href = redirectUrl;
}
});
$('<?php echo esc_attr( $popup_id ); ?> #<?php echo $key; ?>ti-deactivate-yes').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
$(targetElement).unbind('click');
var selectedOption = $(
'<?php echo esc_attr( $popup_id ); ?> input[name="ti-deactivate-option"]:checked');
var data = {
'action': '<?php echo esc_attr( $key ) . '_uninstall_feedback'; ?>',
'nonce': '<?php echo wp_create_nonce( (string) __CLASS__ ); ?>',
'id': selectedOption.parent().attr('ti-option-id'),
'msg': selectedOption.parent().find('textarea').val(),
'type': 'plugin',
'key': '<?php echo esc_attr( $key ); ?>'
};
$.ajax({
type: 'POST',
url: ajaxurl,
data: data,
complete() {
$('body').removeClass('ti-feedback-open');
$('<?php echo esc_attr( $popup_id ); ?>').remove();
if (redirectUrl !== '') {
location.href = redirectUrl;
}
},
beforeSend() {
$('<?php echo esc_attr( $popup_id ); ?>').addClass('sending-feedback');
$('<?php echo esc_attr( $popup_id ); ?> .popup--footer').remove();
$('<?php echo esc_attr( $popup_id ); ?> .popup--body').html('<i class="dashicons dashicons-update-alt"></i>');
}
});
});
});
})(jQuery);
</script>
<?php
do_action( $this->product->get_key() . '_uninstall_feedback_after_js' );
}
/**
* Get the disclosure labels markup.
*
* @return string
*/
private function get_disclosure_labels() {
$disclosure_new_labels = apply_filters( $this->product->get_slug() . '_themeisle_sdk_disclosure_content_labels', [], $this->product );
$disclosure_labels = array_merge(
[
'title' => 'Below is a detailed view of all data that ThemeIsle will receive if you fill in this survey. No domain name, email address or IP addresses are transmited after you submit the survey.',
'items' => [
sprintf( '%s %s version %s %s %s %s', '<strong>', ucwords( $this->product->get_type() ), '</strong>', '<code>', $this->product->get_version(), '</code>' ),
sprintf( '%s Uninstall reason %s %s Selected reason from the above survey %s ', '<strong>', '</strong>', '<i>', '</i>' ),
],
],
$disclosure_new_labels
);
$info_disclosure_content = '<div class="info-disclosure-content"><p>' . wp_kses_post( $disclosure_labels['title'] ) . '</p><ul>';
foreach ( $disclosure_labels['items'] as $disclosure_item ) {
$info_disclosure_content .= sprintf( '<li>%s</li>', wp_kses_post( $disclosure_item ) );
}
$info_disclosure_content .= '</ul></div>';
return $info_disclosure_content;
}
/**
* Randomizes the options array.
*
* @param array $options The options array.
*/
function randomize_options( $options ) {
$new = array();
$keys = array_keys( $options );
shuffle( $keys );
foreach ( $keys as $key ) {
$new[ $key ] = $options[ $key ];
}
return $new;
}
/**
* Called when the deactivate button is clicked.
*/
function post_deactivate() {
check_ajax_referer( (string) __CLASS__, 'nonce' );
$this->post_deactivate_or_cancel();
if ( empty( $_POST['id'] ) ) {
wp_send_json( [] );
return;
}
$this->call_api(
array(
'type' => 'deactivate',
'id' => $_POST['id'],
'comment' => isset( $_POST['msg'] ) ? $_POST['msg'] : '',
)
);
wp_send_json( [] );
}
/**
* Called when the deactivate/cancel button is clicked.
*/
private function post_deactivate_or_cancel() {
if ( ! isset( $_POST['type'] ) || ! isset( $_POST['key'] ) ) {
return;
}
if ( 'theme' !== $_POST['type'] ) {
return;
}
set_transient( 'ti_sdk_pause_' . $_POST['key'], true, self::PAUSE_DEACTIVATE_WINDOW_DAYS * DAY_IN_SECONDS );
}
/**
* Calls the API
*
* @param array $attributes The attributes of the post body.
*
* @return bool Is the request succesfull?
*/
protected function call_api( $attributes ) {
$slug = $this->product->get_slug();
$version = $this->product->get_version();
$attributes['slug'] = $slug;
$attributes['version'] = $version;
$response = wp_remote_post(
self::FEEDBACK_ENDPOINT,
array(
'body' => $attributes,
)
);
return is_wp_error( $response );
}
/**
* Should we load this object?.
*
* @param Product $product Product object.
*
* @return bool Should we load the module?
*/
public function can_load( $product ) {
if ( $this->is_from_partner( $product ) ) {
return false;
}
if ( $product->is_theme() && ( false !== get_transient( 'ti_sdk_pause_' . $product->get_key(), false ) ) ) {
return false;
}
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return true;
}
global $pagenow;
if ( ! isset( $pagenow ) || empty( $pagenow ) ) {
return false;
}
if ( $product->is_plugin() && 'plugins.php' !== $pagenow ) {
return false;
}
if ( $product->is_theme() && 'theme-install.php' !== $pagenow ) {
return false;
}
return true;
}
/**
* Loads module hooks.
*
* @param Product $product Product details.
*
* @return Uninstall_Feedback Current module instance.
*/
public function load( $product ) {
if ( apply_filters( $product->get_key() . '_hide_uninstall_feedback', false ) ) {
return;
}
$this->product = $product;
add_action( 'admin_head', array( $this, 'load_resources' ) );
add_action( 'wp_ajax_' . $this->product->get_key() . '_uninstall_feedback', array( $this, 'post_deactivate' ) );
return $this;
}
}

View File

@ -0,0 +1,418 @@
<?php
/**
* The product model class for ThemeIsle SDK
*
* @package ThemeIsleSDK
* @subpackage Product
* @copyright Copyright (c) 2017, Marius Cristea
* @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
* @since 1.0.0
*/
namespace ThemeisleSDK;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Product model for ThemeIsle SDK.
*/
class Product {
/**
* Define plugin type string.
*/
const PLUGIN_TYPE = 'plugin';
/**
* Define theme type string.
*/
const THEME_TYPE = 'theme';
/**
* If the product has a pro version, contains the pro slug.
*
* @var string $pro_slug Pro slug, if available.
*/
public $pro_slug;
/**
* Current product slug.
*
* @var string $slug THe product slug.
*/
private $slug;
/**
* Product basefile, with the proper metadata.
*
* @var string $basefile The file with headers.
*/
private $basefile;
/**
* Type of the product.
*
* @var string $type The product type ( plugin | theme ).
*/
private $type;
/**
* The file name.
*
* @var string $file The file name.
*/
private $file;
/**
* Product name, fetched from the file headers.
*
* @var string $name The product name.
*/
private $name;
/**
* Product normalized key.
*
* @var string $key The product ready key.
*/
private $key;
/**
* Product store url.
*
* @var string $store_url The store url.
*/
private $store_url;
/**
* Product install timestamp.
*
* @var int $install The date of install.
*/
private $install;
/**
* Product store/author name.
*
* @var string $store_name The store name.
*/
private $store_name;
/**
* Does the product requires license.
*
* @var bool $requires_license Either user needs to activate it with license.
*/
private $requires_license;
/**
* Is the product available on wordpress.org
*
* @var bool $wordpress_available Either is available on WordPress or not.
*/
private $wordpress_available;
/**
* Current version of the product.
*
* @var string $version The product version.
*/
private $version;
/**
* ThemeIsle_SDK_Product constructor.
*
* @param string $basefile Product basefile.
*/
public function __construct( $basefile ) {
if ( ! empty( $basefile ) ) {
if ( is_file( $basefile ) ) {
$this->basefile = $basefile;
$this->setup_from_path();
$this->setup_from_fileheaders();
}
}
$install = get_option( $this->get_key() . '_install', 0 );
if ( 0 === $install ) {
$install = time();
update_option( $this->get_key() . '_install', time() );
}
$this->install = $install;
}
/**
* Setup props from path.
*/
public function setup_from_path() {
$this->file = basename( $this->basefile );
$dir = dirname( $this->basefile );
$this->slug = basename( $dir );
$exts = explode( '.', $this->basefile );
$ext = $exts[ count( $exts ) - 1 ];
if ( 'css' === $ext ) {
$this->type = 'theme';
}
if ( 'php' === $ext ) {
$this->type = 'plugin';
}
$this->key = self::key_ready_name( $this->slug );
}
/**
* Normalize string.
*
* @param string $string the String to be normalized for cron handler.
*
* @return string $name The normalized string.
*/
static function key_ready_name( $string ) {
return str_replace( '-', '_', strtolower( trim( $string ) ) );
}
/**
* Setup props from fileheaders.
*/
public function setup_from_fileheaders() {
$file_headers = array(
'Requires License' => 'Requires License',
'WordPress Available' => 'WordPress Available',
'Pro Slug' => 'Pro Slug',
'Version' => 'Version',
);
if ( 'plugin' === $this->type ) {
$file_headers['Name'] = 'Plugin Name';
$file_headers['AuthorName'] = 'Author';
$file_headers['AuthorURI'] = 'Author URI';
}
if ( 'theme' === $this->type ) {
$file_headers['Name'] = 'Theme Name';
$file_headers['AuthorName'] = 'Author';
$file_headers['AuthorURI'] = 'Author URI';
}
$file_headers = get_file_data( $this->basefile, $file_headers );
$this->name = $file_headers['Name'];
$this->store_name = $file_headers['AuthorName'];
$this->author_url = $file_headers['AuthorURI'];
$this->store_url = $file_headers['AuthorURI'];
$this->requires_license = ( 'yes' === $file_headers['Requires License'] ) ? true : false;
$this->wordpress_available = ( 'yes' === $file_headers['WordPress Available'] ) ? true : false;
$this->pro_slug = ! empty( $file_headers['Pro Slug'] ) ? $file_headers['Pro Slug'] : '';
$this->version = $file_headers['Version'];
}
/**
* Return the product key.
*
* @return string The product key.
*/
public function get_key() {
return $this->key;
}
/**
* Check if the product is either theme or plugin.
*
* @return string Product type.
*/
public function get_type() {
return $this->type;
}
/**
* Return if the product is used as a plugin.
*
* @return bool Is plugin?
*/
public function is_plugin() {
return self::PLUGIN_TYPE === $this->type;
}
/**
* Return if the product is used as a theme.
*
* @return bool Is theme ?
*/
public function is_theme() {
return self::THEME_TYPE === $this->type;
}
/**
* Returns the product slug.
*
* @return string The product slug.
*/
public function get_slug() {
return $this->slug;
}
/**
* The magic var_dump info method.
*
* @return array Debug info.
*/
public function __debugInfo() {
return array(
'name' => $this->name,
'slug' => $this->slug,
'version' => $this->version,
'basefile' => $this->basefile,
'key' => $this->key,
'type' => $this->type,
'store_name' => $this->store_name,
'store_url' => $this->store_url,
'wordpress_available' => $this->wordpress_available,
'requires_license' => $this->requires_license,
);
}
/**
* Getter for product version.
*
* @return string The product version.
*/
public function get_version() {
return $this->version;
}
/**
* Returns current product license, if available.
*
* @return string Return license key, if available.
*/
public function get_license() {
if ( ! $this->requires_license() && ! $this->is_wordpress_available() ) {
return 'free';
}
$license_data = get_option( $this->get_key() . '_license_data', '' );
if ( empty( $license_data ) ) {
return get_option( $this->get_key() . '_license', '' );
}
if ( ! isset( $license_data->key ) ) {
return get_option( $this->get_key() . '_license', '' );
}
return $license_data->key;
}
/**
* Either the product requires license or not.
*
* @return bool Either requires license or not.
*/
public function requires_license() {
return $this->requires_license;
}
/**
* If product is available on wordpress.org or not.
*
* @return bool Either is wp available or not.
*/
public function is_wordpress_available() {
return $this->wordpress_available;
}
/**
* Return friendly name.
*
* @return string Friendly name.
*/
public function get_friendly_name() {
$name = apply_filters( $this->get_key() . '_friendly_name', trim( str_replace( 'Lite', '', $this->get_name() ) ) );
$name = rtrim( $name, '- ()' );
return $name;
}
/**
* Getter for product name.
*
* @return string The product name.
*/
public function get_name() {
return $this->name;
}
/**
* Returns the Store name.
*
* @return string Store name.
*/
public function get_store_name() {
return $this->store_name;
}
/**
* Returns the store url.
*
* @return string The store url.
*/
public function get_store_url() {
if ( strpos( $this->store_url, '/themeisle.com' ) !== false ) {
return 'https://store.themeisle.com/';
}
return $this->store_url;
}
/**
* Returns product basefile, which holds the metaheaders.
*
* @return string The product basefile.
*/
public function get_basefile() {
return $this->basefile;
}
/**
* Get changelog url.
*
* @return string Changelog url.
*/
public function get_changelog() {
return add_query_arg(
[
'name' => rawurlencode( $this->get_name() ),
'edd_action' => 'view_changelog',
],
$this->get_store_url()
);
}
/**
* Returns product filename.
*
* @return string The product filename.
*/
public function get_file() {
return $this->file;
}
/**
* Returns the pro slug, if available.
*
* @return string The pro slug.
*/
public function get_pro_slug() {
return $this->pro_slug;
}
/**
* Return the install timestamp.
*
* @return int The install timestamp.
*/
public function get_install_time() {
return $this->install;
}
/**
* Returns the URL of the product base file.
*
* @param string $path The path to the file.
*
* @return string The URL of the product base file.
*/
public function get_base_url( $path = '/' ) {
if ( $this->type ) {
return plugins_url( $path, $this->basefile );
}
}
}