From 333694c8505bc4a4d90bd36c337c8960a0b219cd Mon Sep 17 00:00:00 2001 From: Leo Fajardo Date: Wed, 6 Apr 2016 00:17:55 +0800 Subject: [PATCH 01/19] [enhancements] [deactivation-modal] Added a new reason in the deactivation feedback form ("It's a temporary deactivation. I'm just debugging an issue."). --- includes/class-freemius.php | 10 ++++++++++ includes/i18n.php | 1 + 2 files changed, 11 insertions(+) diff --git a/includes/class-freemius.php b/includes/class-freemius.php index faa44be5d..0fce72a38 100644 --- a/includes/class-freemius.php +++ b/includes/class-freemius.php @@ -532,6 +532,13 @@ function _get_uninstall_reasons( $user_type = 'long-term' ) { 'input_placeholder' => __fs( 'placeholder-plugin-name', $this->_slug ) ); + $reason_temporary_deactivation = array( + 'id' => 15, + 'text' => __fs( 'reason-temporary-deactivation', $this->_slug ), + 'input_type' => '', + 'input_placeholder' => '' + ); + $reason_other = array( 'id' => 7, 'text' => __fs( 'reason-other', $this->_slug ), @@ -576,6 +583,7 @@ function _get_uninstall_reasons( $user_type = 'long-term' ) { ); } + $long_term_user_reasons[] = $reason_temporary_deactivation; $long_term_user_reasons[] = $reason_other; $uninstall_reasons = array( @@ -594,6 +602,7 @@ function _get_uninstall_reasons( $user_type = 'long-term' ) { 'input_placeholder' => '' ), $reason_found_better_plugin, + $reason_temporary_deactivation, $reason_other ), 'short-term' => array( @@ -628,6 +637,7 @@ function _get_uninstall_reasons( $user_type = 'long-term' ) { 'input_type' => 'textarea', 'input_placeholder' => __fs( 'placeholder-what-did-you-expect', $this->_slug ) ), + $reason_temporary_deactivation, $reason_other ) ); diff --git a/includes/i18n.php b/includes/i18n.php index e8a58ab51..f1fb62b44 100644 --- a/includes/i18n.php +++ b/includes/i18n.php @@ -152,6 +152,7 @@ 'reason-broke-my-site' => __( 'The plugin broke my site', 'freemius' ), 'reason-suddenly-stopped-working' => __( 'The plugin suddenly stopped working', 'freemius' ), 'reason-cant-pay-anymore' => __( "I can't pay for it anymore", 'freemius' ), + 'reason-temporary-deactivation' => __( "It's a temporary deactivation. I'm just debugging an issue.", 'freemius' ), 'reason-other' => _x( 'Other', 'the text of the "other" reason for deactivating the plugin that is shown in the modal box.', 'freemius' ), 'placeholder-plugin-name' => __( "What's the plugin's name?", 'freemius' ), 'placeholder-comfortable-price' => __( 'What price would you feel comfortable paying?', 'freemius' ), From 9d930077404b58e4ad3a568b3fe84e63655fcf1a Mon Sep 17 00:00:00 2001 From: Leo Fajardo Date: Wed, 6 Apr 2016 00:22:27 +0800 Subject: [PATCH 02/19] [enhancements] [deactivation-modal] Made the "Other" input's option in the deactivation feedback form a required field. --- includes/i18n.php | 1 + templates/deactivation-feedback-modal.php | 91 +++++++++++++++++++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/includes/i18n.php b/includes/i18n.php index f1fb62b44..e1774ea71 100644 --- a/includes/i18n.php +++ b/includes/i18n.php @@ -154,6 +154,7 @@ 'reason-cant-pay-anymore' => __( "I can't pay for it anymore", 'freemius' ), 'reason-temporary-deactivation' => __( "It's a temporary deactivation. I'm just debugging an issue.", 'freemius' ), 'reason-other' => _x( 'Other', 'the text of the "other" reason for deactivating the plugin that is shown in the modal box.', 'freemius' ), + 'ask-for-reason-message' => __( 'Kindly tell us the reason so we can improve.', 'freemius' ), 'placeholder-plugin-name' => __( "What's the plugin's name?", 'freemius' ), 'placeholder-comfortable-price' => __( 'What price would you feel comfortable paying?', 'freemius' ), 'reason-couldnt-make-it-work' => __( "I couldn't understand how to make it work", 'freemius' ), diff --git a/templates/deactivation-feedback-modal.php b/templates/deactivation-feedback-modal.php index ed6072dd9..9413a3e97 100644 --- a/templates/deactivation-feedback-modal.php +++ b/templates/deactivation-feedback-modal.php @@ -41,7 +41,8 @@ + ' ' + '', $modal = $(modalHtml), - $deactivateLink = $('#the-list .deactivate > [data-slug=].fs-slug').prev(); + $deactivateLink = $('#the-list .deactivate > [data-slug=].fs-slug').prev(), + selectedReasonID = false; $modal.appendTo($('body')); @@ -54,6 +55,42 @@ function registerEventHandlers() { showModal(); }); + $modal.on( 'input propertychange', '.reason-input input', function() { + if ( ! isOtherReasonSelected() ) { + return; + } + + var reason = $( this ).val().trim(); + + /** + * If reason is not empty, remove the error-message class of the message container + * to change the message color back to default. + */ + if ( reason.length > 0 ) { + $( '.message' ).removeClass( 'error-message' ); + enableDeactivateButton(); + } + }); + + $modal.on( 'blur', '.reason-input input', function() { + var $userReason = $( this ); + + setTimeout(function() { + if ( ! isOtherReasonSelected() ) { + return; + } + + /** + * If reason is empty, add the error-message class to the message container + * to change the message color to red. + */ + if ( 0 === $userReason.val().trim().length ) { + $( '.message' ).addClass( 'error-message' ); + disableDeactivateButton(); + } + }, 150); + }); + $modal.on('click', '.button', function (evt) { evt.preventDefault(); @@ -74,7 +111,12 @@ function registerEventHandlers() { } var $selected_reason = $radio.parents('li:first'), - $input = $selected_reason.find('textarea, input[type="text"]'); + $input = $selected_reason.find('textarea, input[type="text"]'), + userReason = ( 0 !== $input.length ) ? $input.val().trim() : ''; + + if ( isOtherReasonSelected() && ( '' === userReason ) ) { + return; + } $.ajax({ url : ajaxurl, @@ -82,7 +124,7 @@ function registerEventHandlers() { data : { 'action' : 'submit-uninstall-reason', 'reason_id' : $radio.val(), - 'reason_info': ( 0 !== $input.length ) ? $input.val().trim() : '' + 'reason_info' : userReason }, beforeSend: function () { _parent.find('.button').addClass('disabled'); @@ -102,18 +144,33 @@ function registerEventHandlers() { }); $modal.on('click', 'input[type="radio"]', function () { + var $selectedReasonOption = $( this ); + + // If the selection has not changed, do not proceed. + if ( selectedReasonID === $selectedReasonOption.val() ) + return; + + selectedReasonID = $selectedReasonOption.val(); + var _parent = $(this).parents('li:first'); $modal.find('.reason-input').remove(); $modal.find('.button-deactivate').text(''); + enableDeactivateButton(); + if (_parent.hasClass('has-input')) { var inputType = _parent.data('input-type'), inputPlaceholder = _parent.data('input-placeholder'), - reasonInputHtml = '
' + ( ( 'textfield' === inputType ) ? '' : '' ) + '
'; + reasonInputHtml = '
' + ( ( 'textfield' === inputType ) ? '' : '' ) + '
'; _parent.append($(reasonInputHtml)); _parent.find('input, textarea').attr('placeholder', inputPlaceholder).focus(); + + if ( isOtherReasonSelected() ) { + showMessage( '' ); + disableDeactivateButton(); + } } }); @@ -135,6 +192,14 @@ function registerEventHandlers() { }); } + function isOtherReasonSelected() { + // Get the selected radio input element. + var $selectedReasonOption = $modal.find( 'input[type="radio"]:checked' ), + selectedReason = $selectedReasonOption.parent().next().text().trim(); + + return ( 'Other' === selectedReason ); + } + function showModal() { resetModal(); @@ -151,7 +216,9 @@ function closeModal() { } function resetModal() { - $modal.find('.button').removeClass('disabled'); + selectedReasonID = false; + + enableDeactivateButton(); // Uncheck all radio buttons. $modal.find('input[type="radio"]').prop('checked', false); @@ -159,6 +226,8 @@ function resetModal() { // Remove all input fields ( textfield, textarea ). $modal.find('.reason-input').remove(); + $modal.find( '.message' ).hide(); + var $deactivateButton = $modal.find('.button-deactivate'); /* @@ -176,6 +245,18 @@ function resetModal() { } } + function showMessage( message ) { + $modal.find( '.message' ).text( message ).show(); + } + + function enableDeactivateButton() { + $modal.find( '.button-deactivate' ).removeClass( 'disabled' ); + } + + function disableDeactivateButton() { + $modal.find( '.button-deactivate' ).addClass( 'disabled' ); + } + function showPanel(panelType) { $modal.find('.fs-modal-panel').removeClass('active '); $modal.find('[data-panel-id="' + panelType + '"]').addClass('active'); From 4cdbd355aa4ffcabbbf34a6db079476e2f7b314f Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Tue, 5 Apr 2016 22:33:38 -0400 Subject: [PATCH 03/19] [gitignore] [remove] --- .gitignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9ad10acc8..000000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -output/ -assets/img/icon.* - From dc69fe16e1292088162d042b158771f27247d686 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Thu, 7 Apr 2016 18:07:26 -0400 Subject: [PATCH 04/19] [links] [support] Show support forum menu item also if user skips the opt-in. --- includes/class-freemius.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-freemius.php b/includes/class-freemius.php index faa44be5d..c0e6f2e99 100644 --- a/includes/class-freemius.php +++ b/includes/class-freemius.php @@ -5935,7 +5935,7 @@ function _add_default_submenu_items() { return; } - if ( $this->is_registered() || $this->is_anonymous() ) { + if ( ! $this->is_activation_mode() ) { if ( $this->_menu->is_submenu_item_visible( 'support' ) ) { $this->add_submenu_link_item( $this->apply_filters( 'support_forum_submenu', __fs( 'support-forum', $this->_slug ) ), From dcb07de71e71eebf9be7abd0f66062ff5fdc4170 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Thu, 7 Apr 2016 18:08:28 -0400 Subject: [PATCH 05/19] [support-forum] [minor] [bug-fix] Lowercase menu slug on evaluation, otherwise menu slugs with upper case letters cause errors. --- includes/class-freemius.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-freemius.php b/includes/class-freemius.php index c0e6f2e99..a8cae1e25 100644 --- a/includes/class-freemius.php +++ b/includes/class-freemius.php @@ -5639,7 +5639,7 @@ function _redirect_on_clicked_menu_link() { foreach ( $this->_menu_items as $priority => $items ) { foreach ( $items as $item ) { if ( isset( $item['url'] ) ) { - if ( $page === $item['menu_slug'] ) { + if ( $page === strtolower( $item['menu_slug'] ) ) { $this->_logger->log( 'Redirecting to ' . $item['url'] ); fs_redirect( $item['url'] ); From 8e2c39d3fafd4509e6864e1c2c59c69319dc8518 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 11 Apr 2016 19:42:23 -0400 Subject: [PATCH 06/19] [sync] Send plugin and theme updates diff with the install sync event. --- includes/class-freemius.php | 377 +++++++++++++++++++- includes/debug/class-fs-debug-bar-panel.php | 2 + includes/i18n.php | 2 + 3 files changed, 372 insertions(+), 9 deletions(-) diff --git a/includes/class-freemius.php b/includes/class-freemius.php index 747413871..c7d7f2254 100644 --- a/includes/class-freemius.php +++ b/includes/class-freemius.php @@ -826,7 +826,7 @@ function is_activation_mode() { * * @return array[string]array */ - private function get_active_plugins() { + private static function get_active_plugins() { self::require_plugin_essentials(); $active_plugin = array(); @@ -839,6 +839,90 @@ private function get_active_plugins() { return $active_plugin; } + /** + * Get collection of all plugins. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return array Key is the plugin file path and the value is an array of the plugin data. + */ + private static function get_all_plugins() { + self::require_plugin_essentials(); + + $all_plugins = get_plugins(); + $active_plugins_basenames = get_option( 'active_plugins' ); + + foreach ( $all_plugins as $basename => &$data ) { + // By default set to inactive (next foreach update the active plugins). + $data['is_active'] = false; + // Enrich with plugin slug. + $data['slug'] = self::get_plugin_slug( $basename ); + } + + // Flag active plugins. + foreach ( $active_plugins_basenames as $basename ) { + if ( isset( $all_plugins[ $basename ] ) ) { + $all_plugins[ $basename ]['is_active'] = true; + } + } + + return $all_plugins; + } + + + /** + * Cached result of get_site_transient( 'update_plugins' ) + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @var object + */ + private static $_plugins_info; + /** + * Helper function to get specified plugin's slug. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @param $basename + * + * @return string + */ + private static function get_plugin_slug($basename) { + if ( ! isset( self::$_plugins_info ) ) { + self::$_plugins_info = get_site_transient( 'update_plugins' ); + } + + $slug = ''; + + if ( is_object( self::$_plugins_info ) ) { + if ( isset( self::$_plugins_info->no_update ) && + isset( self::$_plugins_info->no_update[ $basename ] ) && + ! empty( self::$_plugins_info->no_update[ $basename ]->slug ) + ) { + $slug = self::$_plugins_info->no_update[ $basename ]->slug; + } else if ( isset( self::$_plugins_info->response ) && + isset( self::$_plugins_info->response[ $basename ] ) && + ! empty( self::$_plugins_info->response[ $basename ]->slug ) + ) { + $slug = self::$_plugins_info->response[ $basename ]->slug; + } + } + + if ( empty( $slug ) ) { + // Try to find slug from FS data. + $slug = self::find_slug_by_basename( $basename ); + } + + if ( empty( $slug ) ) { + // Fallback to plugin's folder name. + $slug = dirname( $basename ); + } + + return $slug; + } private static $_statics_loaded = false; @@ -1595,7 +1679,7 @@ private function get_email_sections() { // Retrieve the cURL version information so that we can get the version number below. $curl_version_information = curl_version(); - $active_plugin = $this->get_active_plugins(); + $active_plugin = self::get_active_plugins(); // Generate the list of active plugins separated by new line. $active_plugin_string = ''; @@ -3280,17 +3364,287 @@ private function update_plugin_version_event() { // $this->sync_install( array(), true ); } + /** + * Return a list of modified plugins since the last sync. + * + * Note: + * There's no point to store a plugins counter since even if the number of + * plugins didn't change, we still need to check if the versions are all the + * same and the activity state is similar. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return array|false + */ + private function get_plugins_data_for_api() { + // Alias. + $option_name = 'all_plugins'; + + $all_cached_plugins = self::$_accounts->get_option( $option_name ); + + if ( ! is_object( $all_cached_plugins ) ) { + $all_cached_plugins = (object) array( + 'timestamp' => '', + 'md5' => '', + 'plugins' => array(), + ); + } + + $time = time(); + + if (!empty($all_cached_plugins->timestamp) && + ($time - $all_cached_plugins->timestamp) < WP_FS__TIME_5_MIN_IN_SEC + ){ + // Don't send plugin updates if last update was in the past 5 min. + return false; + } + + // Write timestamp to lock the logic. + $all_cached_plugins->timestamp = $time; + self::$_accounts->set_option( $option_name, $all_cached_plugins, true ); + + // Reload options from DB. + self::$_accounts->load(true); + $all_cached_plugins = self::$_accounts->get_option( $option_name ); + + if ($time != $all_cached_plugins->timestamp) + { + // If timestamp is different, then another thread captured the lock. + return false; + } + + // Check if there's a change in plugins. + $all_plugins = self::get_all_plugins(); + + // Check if plugins changed. + ksort( $all_plugins ); + + $plugins_signature = ''; + foreach ( $all_plugins as $basename => $data ) { + $plugins_signature .= $data['slug'] . ',' . + $data['Version'] . ',' . + ( $data['is_active'] ? '1' : '0' ) . ';'; + } + + // Check if plugins status changed (version or active/inactive). + $plugins_changed = ( $all_cached_plugins->md5 !== md5( $plugins_signature ) ); + + $plugins_update_data = array(); + + if ( $plugins_changed ) { + // Change in plugins, report changes. + + // Update existing plugins info. + foreach ( $all_cached_plugins->plugins as $basename => $data ) { + if ( ! isset( $all_plugins[ $basename ] ) ) { + // Plugin uninstalled. + $uninstalled_plugin_data = $data; + $uninstalled_plugin_data['is_active'] = false; + $uninstalled_plugin_data['is_uninstalled'] = true; + $plugins_update_data[] = $uninstalled_plugin_data; + + unset( $all_plugins[ $basename ] ); + unset( $all_cached_plugins->plugins[ $basename ] ); + } else if ( $data['is_active'] !== $all_plugins[ $basename ]['is_active'] || + $data['version'] !== $all_plugins[ $basename ]['Version'] + ) { + // Plugin activated or deactivated, or version changed. + $all_cached_plugins->plugins[$basename]['is_active'] = $all_plugins[ $basename ]['is_active']; + $all_cached_plugins->plugins[$basename]['version'] = $all_plugins[ $basename ]['Version']; + + $plugins_update_data[] = $all_cached_plugins->plugins[$basename]; + } + } + + // Find new plugins that weren't yet seen before. + foreach ( $all_plugins as $basename => $data ) { + if ( ! isset( $all_cached_plugins->plugins[ $basename ] ) ) { + // New plugin. + $new_plugin = array( + 'slug' => $data['slug'], + 'version' => $data['Version'], + 'title' => $data['Name'], + 'is_active' => $data['is_active'], + 'is_uninstalled' => false, + ); + + $plugins_update_data[] = $new_plugin; + $all_cached_plugins->plugins[ $basename ] = $new_plugin; + } + } + + $all_cached_plugins->md5 = md5( $plugins_signature ); + $all_cached_plugins->timestamp = $time; + self::$_accounts->set_option( $option_name, $all_cached_plugins, true ); + } + + return $plugins_update_data; + } + + /** + * Return a list of modified themes since the last sync. + * + * Note: + * There's no point to store a themes counter since even if the number of + * themes didn't change, we still need to check if the versions are all the + * same and the activity state is similar. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return array|false + */ + private function get_themes_data_for_api() { + // Alias. + $option_name = 'all_themes'; + + $all_cached_themes = self::$_accounts->get_option( $option_name ); + + if ( ! is_object( $all_cached_themes ) ) { + $all_cached_themes = (object) array( + 'timestamp' => '', + 'md5' => '', + 'themes' => array(), + ); + } + + $time = time(); + + if (!empty($all_cached_themes->timestamp) && + ($time - $all_cached_themes->timestamp) < WP_FS__TIME_5_MIN_IN_SEC + ){ + // Don't send theme updates if last update was in the past 5 min. + return false; + } + + // Write timestamp to lock the logic. + $all_cached_themes->timestamp = $time; + self::$_accounts->set_option( $option_name, $all_cached_themes, true ); + + // Reload options from DB. + self::$_accounts->load(true); + $all_cached_themes = self::$_accounts->get_option( $option_name ); + + if ($time != $all_cached_themes->timestamp) + { + // If timestamp is different, then another thread captured the lock. + return false; + } + + // Get active theme. + $active_theme = wp_get_theme(); + + // Check if there's a change in themes. + $all_themes = wp_get_themes(); + + // Check if themes changed. + ksort( $all_themes ); + + $themes_signature = ''; + foreach ( $all_themes as $slug => $data ) { + $is_active = ( $slug === $active_theme->stylesheet ); + $themes_signature .= $slug . ',' . + $data->version . ',' . + ( $is_active ? '1' : '0' ) . ';'; + } + + // Check if themes status changed (version or active/inactive). + $themes_changed = ( $all_cached_themes->md5 !== md5( $themes_signature ) ); + + $themes_update_data = array(); + + if ( $themes_changed ) { + // Change in themes, report changes. + + // Update existing themes info. + foreach ( $all_cached_themes->themes as $slug => $data ) { + $is_active = ( $slug === $active_theme->stylesheet ); + + if ( ! isset( $all_themes[ $slug ] ) ) { + // Plugin uninstalled. + $uninstalled_theme_data = $data; + $uninstalled_theme_data['is_active'] = false; + $uninstalled_theme_data['is_uninstalled'] = true; + $themes_update_data[] = $uninstalled_theme_data; + + unset( $all_themes[ $slug ] ); + unset( $all_cached_themes->themes[ $slug ] ); + } else if ( $data['is_active'] !== $is_active || + $data['version'] !== $all_themes[ $slug ]->version + ) { + // Plugin activated or deactivated, or version changed. + + $all_cached_themes->themes[$slug]['is_active'] = $is_active; + $all_cached_themes->themes[$slug]['version'] = $all_themes[ $slug ]->version; + + $themes_update_data[] = $all_cached_themes->themes[$slug]; + } + } + + // Find new themes that weren't yet seen before. + foreach ( $all_themes as $slug => $data ) { + if ( ! isset( $all_cached_themes->themes[ $slug ] ) ) { + $is_active = ( $slug === $active_theme->stylesheet ); + + // New plugin. + $new_plugin = array( + 'slug' => $slug, + 'version' => $data->version, + 'title' => $data->name, + 'is_active' => $is_active, + 'is_uninstalled' => false, + ); + + $themes_update_data[] = $new_plugin; + $all_cached_themes->themes[ $slug ] = $new_plugin; + } + } + + $all_cached_themes->md5 = md5( $themes_signature ); + $all_cached_themes->timestamp = time(); + self::$_accounts->set_option( $option_name, $all_cached_themes, true ); + } + + return $themes_update_data; + } + /** * Update install details. * * @author Vova Feldman (@svovaf) * @since 1.1.2 * - * @param string[] string $override + * @param string[] string $override + * @param bool $include_plugins Since 1.1.8 by default include plugin changes. + * @param bool $include_themes Since 1.1.8 by default include plugin changes. * * @return array */ - private function get_install_data_for_api( $override = array() ) { + private function get_install_data_for_api( + array $override, + $include_plugins = true, + $include_themes = true + ) { + /** + * @since 1.1.8 Also send plugin updates. + */ + if ( $include_plugins && ! isset( $override['plugins'] ) ) { + $plugins = $this->get_plugins_data_for_api(); + if ( ! empty( $plugins ) ) { + $override['plugins'] = $plugins; + } + } + /** + * @since 1.1.8 Also send themes updates. + */ + if ( $include_themes && ! isset( $override['themes'] ) ) { + $themes = $this->get_themes_data_for_api(); + if ( ! empty( $themes ) ) { + $override['themes'] = $themes; + } + } + return array_merge( array( 'version' => $this->get_plugin_version(), 'is_premium' => $this->is_premium(), @@ -3312,7 +3666,7 @@ private function get_install_data_for_api( $override = array() ) { * @author Vova Feldman (@svovaf) * @since 1.0.9 * - * @param string[] string $override + * @param string[]string $override * @param bool $flush * * @return false|object|string @@ -3340,7 +3694,10 @@ private function send_install_update( $override = array(), $flush = false ) { } else { $special[ $p ] = $v; - if ( isset( $override[ $p ] ) ) { + if ( isset( $override[ $p ] ) || + 'plugins' === $p || + 'themes' === $p + ) { $special_override = true; } } @@ -3349,7 +3706,7 @@ private function send_install_update( $override = array(), $flush = false ) { if ( $special_override || 0 < count( $params ) ) { // Add special params only if has at least one // standard param, or if explicitly requested to - // override a special param or a pram which is not exist + // override a special param or a param which is not exist // in the install object. $params = array_merge( $params, $special ); } @@ -3359,6 +3716,8 @@ private function send_install_update( $override = array(), $flush = false ) { // Update last install sync timestamp. $this->_storage->install_sync_timestamp = time(); + $params['uid'] = $this->get_anonymous_id(); + // Send updated values to FS. $site = $this->get_api_site_scope()->call( '/', 'put', $params ); @@ -5498,7 +5857,7 @@ private function install_with_current_user( $redirect = true ) { 'post', $this->get_install_data_for_api( array( 'uid' => $this->get_anonymous_id(), - ) ) + ), false, false ) ); if ( isset( $install->error ) ) { @@ -5542,7 +5901,7 @@ private function _activate_addon_account( Freemius $parent_fs ) { 'post', $this->get_install_data_for_api( array( 'uid' => $this->get_anonymous_id(), - ) ) + ), false, false ) ); if ( isset( $addon_install->error ) ) { diff --git a/includes/debug/class-fs-debug-bar-panel.php b/includes/debug/class-fs-debug-bar-panel.php index 7bf7c43c6..1c3259bdf 100644 --- a/includes/debug/class-fs-debug-bar-panel.php +++ b/includes/debug/class-fs-debug-bar-panel.php @@ -55,6 +55,8 @@ function render() {

+ +
__( 'Verified', 'freemius' ), 'plugin' => __( 'Plugin', 'freemius' ), 'plugins' => __( 'Plugins', 'freemius' ), + 'themes' => __( 'Themes', 'freemius' ), 'path' => _x( 'Path', 'as file/folder path', 'freemius' ), 'title' => __( 'Title', 'freemius' ), 'free-version' => __( 'Free version', 'freemius' ), @@ -220,6 +221,7 @@ 'clear-api-cache' => __( 'Clear API Cache', 'freemius' ), 'sync-data-from-server' => __( 'Sync Data From Server', 'freemius' ), 'scheduled-crons' => __( 'Scheduled Crons', 'freemius' ), + 'plugins-themes-sync' => __( 'Plugins & Themes Sync', 'freemius' ), #endregion Debug #region Expressions From 54ba0ae18c834db9cce46d63caa830d476f4e027 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 11 Apr 2016 19:43:28 -0400 Subject: [PATCH 07/19] [option-manager] [bug-fix] When loading an option manager with a flush, make sure clean the previous options first. --- includes/managers/class-fs-option-manager.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/includes/managers/class-fs-option-manager.php b/includes/managers/class-fs-option-manager.php index dbadf9fff..19c706964 100755 --- a/includes/managers/class-fs-option-manager.php +++ b/includes/managers/class-fs-option-manager.php @@ -100,6 +100,11 @@ function load( $flush = false ) { $option_name = $this->_get_option_manager_name(); if ( $flush || ! isset( $this->_options ) ) { + if ( isset( $this->_options ) ) { + // Clear prev options. + $this->clear(); + } + if ( ! WP_FS__DEBUG_SDK ) { $this->_options = wp_cache_get( $option_name, WP_FS__SLUG ); } From 89b55d848fdcd0c1d11ed7731a661f2d0d20aa74 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 11 Apr 2016 19:43:38 -0400 Subject: [PATCH 08/19] [version] --- start.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/start.php b/start.php index e85c7d85c..f2e5e3665 100644 --- a/start.php +++ b/start.php @@ -10,7 +10,12 @@ exit; } - $this_sdk_version = '1.1.7.5'; + /** + * Freemius SDK Version. + * + * @var string + */ + $this_sdk_version = '1.1.8'; #region SDK Selection Logic -------------------------------------------------------------------- From 018e048eac37bc77ec2f5320f1091334d526c559 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 11 Apr 2016 19:44:27 -0400 Subject: [PATCH 09/19] [debug] [debug-bar] Added info about plugin/theme last update into the FS debug bar panel. --- templates/debug/plugins-themes-sync.php | 68 +++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 templates/debug/plugins-themes-sync.php diff --git a/templates/debug/plugins-themes-sync.php b/templates/debug/plugins-themes-sync.php new file mode 100644 index 000000000..a7616220a --- /dev/null +++ b/templates/debug/plugins-themes-sync.php @@ -0,0 +1,68 @@ +get_option( 'all_plugins' ); + $all_themes = $fs_options->get_option( 'all_themes' ); +?> +

+ + + + + + + + + + + + + + + + + + + + + + + + +
plugins ) ?>timestamp ) && is_numeric( $all_plugins->timestamp ) ) { + $diff = abs( WP_FS__SCRIPT_START_TIME - $all_plugins->timestamp ); + $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? + $diff . ' ' . __fs( 'sec' ) : + human_time_diff( WP_FS__SCRIPT_START_TIME, $all_plugins->timestamp ); + + if ( WP_FS__SCRIPT_START_TIME < $all_plugins->timestamp ) { + printf( __fs( 'in-x' ), $human_diff ); + } else { + printf( __fs( 'x-ago' ), $human_diff ); + } + } + ?>
themes ) ?>timestamp ) && is_numeric( $all_themes->timestamp ) ) { + $diff = abs( WP_FS__SCRIPT_START_TIME - $all_themes->timestamp ); + $human_diff = ( $diff < MINUTE_IN_SECONDS ) ? + $diff . ' ' . __fs( 'sec' ) : + human_time_diff( WP_FS__SCRIPT_START_TIME, $all_themes->timestamp ); + + if ( WP_FS__SCRIPT_START_TIME < $all_themes->timestamp ) { + printf( __fs( 'in-x' ), $human_diff ); + } else { + printf( __fs( 'x-ago' ), $human_diff ); + } + } + ?>
From 0d906fd921cf806360cc1effc5cfbced05985568 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 18 Apr 2016 20:04:06 -0400 Subject: [PATCH 10/19] [menu] add_object_page was deprecated at WP 4.5. Closed #44 --- includes/class-freemius.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-freemius.php b/includes/class-freemius.php index c7d7f2254..ac35f749d 100644 --- a/includes/class-freemius.php +++ b/includes/class-freemius.php @@ -972,7 +972,7 @@ static function add_debug_page() { if ( WP_FS__DEV_MODE ) { // Add top-level debug menu item. - $hook = add_object_page( + $hook = add_menu_page( $title, $title, 'manage_options', From b18ccc0287f8e097882a35cf73a96fd431e51ebc Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 18 Apr 2016 20:06:18 -0400 Subject: [PATCH 11/19] [opt-in] Optimized opt-in permissions messaging to allow future capture of plugins & themes (like WooCommerce does). --- includes/i18n.php | 6 ++++-- templates/connect.php | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/includes/i18n.php b/includes/i18n.php index 2b94211be..167ec3240 100644 --- a/includes/i18n.php +++ b/includes/i18n.php @@ -181,9 +181,11 @@ 'permissions-profile' => __( 'Your Profile Overview', 'freemius' ), 'permissions-profile_desc' => __( 'Name and email address', 'freemius' ), 'permissions-site' => __( 'Your Site Overview', 'freemius' ), - 'permissions-site_desc' => __( 'Site address, WordPress version, PHP Version', 'freemius' ), - 'permissions-events' => __( 'Plugin Events', 'freemius' ), + 'permissions-site_desc' => __( 'Site URL, WP version, PHP info, plugins & themes', 'freemius' ), + 'permissions-events' => __( 'Current Plugin Events', 'freemius' ), 'permissions-events_desc' => __( 'Activation, deactivation and uninstall', 'freemius' ), + 'permissions-plugins_themes' => __( 'Plugins & Themes', 'freemius' ), + 'permissions-plugins_themes_desc' => __( 'Titles, versions and state.', 'freemius' ), 'permissions-newsletter' => __( 'Newsletter', 'freemius' ), 'permissions-newsletter_desc' => __( 'Updates, announcements, marketing, no spam', 'freemius' ), 'privacy-policy' => __( 'Privacy Policy', 'freemius' ), diff --git a/templates/connect.php b/templates/connect.php index 00399a8b0..50ca4b006 100755 --- a/templates/connect.php +++ b/templates/connect.php @@ -121,19 +121,19 @@ class="button button-secondary" tabindex="2"> // Set core permission list items. $permissions = array( - 'profile' => array( + 'profile' => array( 'icon-class' => 'dashicons dashicons-admin-users', 'label' => __fs( 'permissions-profile' ), 'desc' => __fs( 'permissions-profile_desc' ), 'priority' => 5, ), - 'site' => array( - 'icon-class' => 'dashicons dashicons-wordpress', + 'site' => array( + 'icon-class' => 'dashicons dashicons-admin-settings', 'label' => __fs( 'permissions-site' ), 'desc' => __fs( 'permissions-site_desc' ), 'priority' => 10, ), - 'events' => array( + 'events' => array( 'icon-class' => 'dashicons dashicons-admin-plugins', 'label' => __fs( 'permissions-events' ), 'desc' => __fs( 'permissions-events_desc' ), From 8e864b2e3538f2688f9c94971761ee2580babde8 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 18 Apr 2016 20:09:51 -0400 Subject: [PATCH 12/19] [add-ons] [multi-cycle] [ui] Added visual support to show add-ons multi billing-cycle pricing right in the native dialog box. --- assets/css/admin/add-ons.css | 4 +- assets/scss/admin/add-ons.scss | 320 ++++++++++------ includes/entities/class-fs-pricing.php | 91 +++++ includes/fs-plugin-info-dialog.php | 481 ++++++++++++++++--------- includes/i18n.php | 11 + 5 files changed, 628 insertions(+), 279 deletions(-) diff --git a/assets/css/admin/add-ons.css b/assets/css/admin/add-ons.css index f3774522e..0c2df5ab4 100644 --- a/assets/css/admin/add-ons.css +++ b/assets/css/admin/add-ons.css @@ -1,2 +1,2 @@ -#fs_addons .fs-cards-list{list-style:none}#fs_addons .fs-cards-list .fs-card{float:left;height:152px;width:310px;padding:0;margin:0 0 30px 30px;font-size:14px;list-style:none;border:1px solid #ddd;cursor:pointer;position:relative}#fs_addons .fs-cards-list .fs-card .fs-overlay{position:absolute;left:0;right:0;bottom:0;top:0;z-index:9}#fs_addons .fs-cards-list .fs-card .fs-inner{background-color:#fff;overflow:hidden;height:100%;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner ul{-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s;left:0;right:0;top:0;position:absolute}#fs_addons .fs-cards-list .fs-card .fs-inner li{list-style:none;line-height:18px;padding:0 15px;width:100%;display:block;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner{padding:0;margin:0;line-height:0;display:block;height:100px;background-repeat:repeat-x;background-size:100% 100%;-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-title{margin:10px 0 0 0;height:18px;overflow:hidden;color:#000;white-space:nowrap;text-overflow:ellipsis;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-offer{font-size:0.9em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-description{background-color:#f9f9f9;padding:10px 15px 100px 15px;border-top:1px solid #eee;margin:0 0 10px 0;color:#777}@media screen and (min-width: 960px){#fs_addons .fs-cards-list .fs-card:hover .fs-overlay{border:2px solid #29abe1;margin-left:-1px;margin-top:-1px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner ul{top:-100px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-title,#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-offer{color:#29abe1}} -#TB_window,#TB_window iframe{width:772px !important}#plugin-information #section-description h2,#plugin-information #section-description h3,#plugin-information #section-description p,#plugin-information #section-description b,#plugin-information #section-description i,#plugin-information #section-description blockquote,#plugin-information #section-description li,#plugin-information #section-description ul,#plugin-information #section-description ol{clear:none}#plugin-information #section-description .fs-selling-points{padding-bottom:10px;border-bottom:1px solid #ddd}#plugin-information #section-description .fs-selling-points ul{margin:0}#plugin-information #section-description .fs-selling-points ul li{padding:0;list-style:none outside none}#plugin-information #section-description .fs-selling-points ul li i.dashicons{color:#71ae00;font-size:3em;vertical-align:middle;line-height:30px;float:left;margin:0 0 0 -15px}#plugin-information #section-description .fs-selling-points ul li h3{margin:1em 30px !important}#plugin-information #section-description .fs-screenshots:after{content:"";display:table;clear:both}#plugin-information #section-description .fs-screenshots ul{list-style:none;margin:0}#plugin-information #section-description .fs-screenshots ul li{width:225px;height:225px;float:left;margin-bottom:20px;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}#plugin-information #section-description .fs-screenshots ul li a{display:block;width:100%;height:100%;border:1px solid;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);background-size:cover}#plugin-information #section-description .fs-screenshots ul li.odd{margin-right:20px}#plugin-information .plugin-information-pricing{background:#FFFEEC;margin:-16px;padding:20px;border-bottom:1px solid #DDD}#plugin-information .plugin-information-pricing h3{margin-top:0}#plugin-information .plugin-information-pricing .button{width:100%;text-align:center;font-weight:bold;text-transform:uppercase;font-size:1.1em}#plugin-information .plugin-information-pricing label{white-space:nowrap}#plugin-information .plugin-information-pricing ul.fs-trial-terms{font-size:0.9em}#plugin-information .plugin-information-pricing ul.fs-trial-terms i{float:left;margin:0 0 0 -15px}#plugin-information .plugin-information-pricing ul.fs-trial-terms li{margin:10px 0 0 0}#plugin-information #section-features .fs-features{margin:-20px -26px}#plugin-information #section-features table{width:100%;border-spacing:0;border-collapse:separate}#plugin-information #section-features table thead th{padding:10px 0}#plugin-information #section-features table thead .fs-price{color:#71ae00;font-weight:normal;display:block;text-align:center}#plugin-information #section-features table tbody td{border-top:1px solid #ccc;padding:10px 0;text-align:center;width:100px;color:#71ae00}#plugin-information #section-features table tbody td:first-child{text-align:left;width:auto;color:inherit;padding-left:26px}#plugin-information #section-features table tbody tr.fs-odd td{background:#fefefe}#plugin-information #section-features .dashicons-yes{width:30px;height:30px;font-size:30px}@media screen and (max-width: 961px){#fs_addons .fs-cards-list .fs-card{height:265px}} +#fs_addons .fs-cards-list{list-style:none}#fs_addons .fs-cards-list .fs-card{float:left;height:152px;width:310px;padding:0;margin:0 0 30px 30px;font-size:14px;list-style:none;border:1px solid #ddd;cursor:pointer;position:relative}#fs_addons .fs-cards-list .fs-card .fs-overlay{position:absolute;left:0;right:0;bottom:0;top:0;z-index:9}#fs_addons .fs-cards-list .fs-card .fs-inner{background-color:#fff;overflow:hidden;height:100%;position:relative}#fs_addons .fs-cards-list .fs-card .fs-inner ul{-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s;left:0;right:0;top:0;position:absolute}#fs_addons .fs-cards-list .fs-card .fs-inner li{list-style:none;line-height:18px;padding:0 15px;width:100%;display:block;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-card-banner{padding:0;margin:0;line-height:0;display:block;height:100px;background-repeat:repeat-x;background-size:100% 100%;-moz-transition:all,0.15s;-o-transition:all,0.15s;-ms-transition:all,0.15s;-webkit-transition:all,0.15s;transition:all,0.15s}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-title{margin:10px 0 0 0;height:18px;overflow:hidden;color:#000;white-space:nowrap;text-overflow:ellipsis;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-offer{font-size:0.9em}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-description{background-color:#f9f9f9;padding:10px 15px 100px 15px;border-top:1px solid #eee;margin:0 0 10px 0;color:#777}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-tag{position:absolute;top:10px;right:0px;background:greenyellow;display:block;padding:2px 10px;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.3);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.3);box-shadow:1px 1px 1px rgba(0,0,0,0.3);text-transform:uppercase;font-size:0.9em;font-weight:bold}#fs_addons .fs-cards-list .fs-card .fs-inner .fs-cta .button{position:absolute;top:112px;right:10px}@media screen and (min-width: 960px){#fs_addons .fs-cards-list .fs-card:hover .fs-overlay{border:2px solid #29abe1;margin-left:-1px;margin-top:-1px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner ul{top:-100px}#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-title,#fs_addons .fs-cards-list .fs-card:hover .fs-inner .fs-offer{color:#29abe1}} +#TB_window,#TB_window iframe{width:772px !important}#plugin-information #section-description h2,#plugin-information #section-description h3,#plugin-information #section-description p,#plugin-information #section-description b,#plugin-information #section-description i,#plugin-information #section-description blockquote,#plugin-information #section-description li,#plugin-information #section-description ul,#plugin-information #section-description ol{clear:none}#plugin-information #section-description .fs-selling-points{padding-bottom:10px;border-bottom:1px solid #ddd}#plugin-information #section-description .fs-selling-points ul{margin:0}#plugin-information #section-description .fs-selling-points ul li{padding:0;list-style:none outside none}#plugin-information #section-description .fs-selling-points ul li i.dashicons{color:#71ae00;font-size:3em;vertical-align:middle;line-height:30px;float:left;margin:0 0 0 -15px}#plugin-information #section-description .fs-selling-points ul li h3{margin:1em 30px !important}#plugin-information #section-description .fs-screenshots:after{content:"";display:table;clear:both}#plugin-information #section-description .fs-screenshots ul{list-style:none;margin:0}#plugin-information #section-description .fs-screenshots ul li{width:225px;height:225px;float:left;margin-bottom:20px;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}#plugin-information #section-description .fs-screenshots ul li a{display:block;width:100%;height:100%;border:1px solid;-moz-box-shadow:1px 1px 1px rgba(0,0,0,0.2);-webkit-box-shadow:1px 1px 1px rgba(0,0,0,0.2);box-shadow:1px 1px 1px rgba(0,0,0,0.2);background-size:cover}#plugin-information #section-description .fs-screenshots ul li.odd{margin-right:20px}#plugin-information .plugin-information-pricing{margin:-16px;border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan h3{margin-top:0;padding:20px;font-size:16px}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper{border-bottom:1px solid #ddd}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab{cursor:pointer;position:relative;padding:0 10px;font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab label{text-transform:uppercase;color:green;background:greenyellow;position:absolute;left:-1px;right:-1px;bottom:100%;border:1px solid darkgreen;padding:2px;text-align:center;font-size:0.9em;line-height:1em}#plugin-information .plugin-information-pricing .fs-plan .nav-tab-wrapper .nav-tab.nav-tab-active{cursor:default;background:#fffeec;border-bottom-color:#fffeec}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle h3{background:#fffeec;margin:0;padding-bottom:0;color:#0073aa}#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .nav-tab-wrapper,#plugin-information .plugin-information-pricing .fs-plan.fs-single-cycle .fs-billing-frequency{display:none}#plugin-information .plugin-information-pricing .fs-plan .fs-pricing-body{background:#fffeec;padding:20px}#plugin-information .plugin-information-pricing .fs-plan .button{width:100%;text-align:center;font-weight:bold;text-transform:uppercase;font-size:1.1em}#plugin-information .plugin-information-pricing .fs-plan label{white-space:nowrap}#plugin-information .plugin-information-pricing .fs-plan var{font-style:normal}#plugin-information .plugin-information-pricing .fs-plan .fs-billing-frequency,#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-align:center;display:block;font-weight:bold;margin-bottom:10px;text-transform:uppercase;background:#F3F3F3;padding:2px;border:1px solid #ccc}#plugin-information .plugin-information-pricing .fs-plan .fs-annual-discount{text-transform:none;color:green;background:greenyellow}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms{font-size:0.9em}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms i{float:left;margin:0 0 0 -15px}#plugin-information .plugin-information-pricing .fs-plan ul.fs-trial-terms li{margin:10px 0 0 0}#plugin-information #section-features .fs-features{margin:-20px -26px}#plugin-information #section-features table{width:100%;border-spacing:0;border-collapse:separate}#plugin-information #section-features table thead th{padding:10px 0}#plugin-information #section-features table thead .fs-price{color:#71ae00;font-weight:normal;display:block;text-align:center}#plugin-information #section-features table tbody td{border-top:1px solid #ccc;padding:10px 0;text-align:center;width:100px;color:#71ae00}#plugin-information #section-features table tbody td:first-child{text-align:left;width:auto;color:inherit;padding-left:26px}#plugin-information #section-features table tbody tr.fs-odd td{background:#fefefe}#plugin-information #section-features .dashicons-yes{width:30px;height:30px;font-size:30px}@media screen and (max-width: 961px){#fs_addons .fs-cards-list .fs-card{height:265px}} diff --git a/assets/scss/admin/add-ons.scss b/assets/scss/admin/add-ons.scss index bf799cb67..f1636cdef 100755 --- a/assets/scss/admin/add-ons.scss +++ b/assets/scss/admin/add-ons.scss @@ -8,76 +8,75 @@ .fs-card { - float: left; -// height: 185px; // With reviews/ratings - height: 152px; - width: 310px; - padding: 0; - margin: 0 0 30px 30px; - font-size: 14px; + float: left; + // height: 185px; // With reviews/ratings + height: 152px; + width: 310px; + padding: 0; + margin: 0 0 30px 30px; + font-size: 14px; list-style: none; - border: 1px solid #ddd; - cursor: pointer; - position: relative; + border: 1px solid #ddd; + cursor: pointer; + position: relative; .fs-overlay { position: absolute; - left: 0; - right: 0; - bottom: 0; - top: 0; - z-index: 9; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 9; } .fs-inner { background-color: #fff; - overflow: hidden; - height: 100%; - position: relative; + overflow: hidden; + height: 100%; + position: relative; ul { @include transition(all, 0.15s); - left: 0; - right: 0; - top: 0; + left: 0; + right: 0; + top: 0; position: absolute; - } li { - list-style: none; + list-style: none; line-height: 18px; - padding: 0 15px; - width: 100%; - display: block; + padding: 0 15px; + width: 100%; + display: block; @include box-sizing(border-box); } .fs-card-banner { - padding: 0; - margin: 0; - line-height: 0; - display: block; - height: 100px; + padding: 0; + margin: 0; + line-height: 0; + display: block; + height: 100px; background-repeat: repeat-x; - background-size: 100% 100%; + background-size: 100% 100%; @include transition(all, 0.15s); } .fs-title { - margin: 10px 0 0 0; - height: 18px; - overflow: hidden; - color: #000; - white-space: nowrap; + margin: 10px 0 0 0; + height: 18px; + overflow: hidden; + color: #000; + white-space: nowrap; text-overflow: ellipsis; - font-weight: bold; + font-weight: bold; } .fs-offer @@ -88,10 +87,34 @@ .fs-description { background-color: #f9f9f9; - padding: 10px 15px 100px 15px; - border-top: 1px solid #eee; - margin: 0 0 10px 0; - color: #777; + padding: 10px 15px 100px 15px; + border-top: 1px solid #eee; + margin: 0 0 10px 0; + color: #777; + } + + .fs-tag + { + position: absolute; + top: 10px; + right: 0px; + background: greenyellow; + display: block; + padding: 2px 10px; + @include box-shadow(1px 1px 1px rgba(0,0,0,0.3)); + text-transform: uppercase; + font-size: 0.9em; + font-weight: bold; + } + + .fs-cta + { + .button + { + position: absolute; + top: 112px; + right: 10px; + } } } @@ -149,7 +172,7 @@ .fs-selling-points { padding-bottom: 10px; - border-bottom: 1px solid #ddd; + border-bottom: 1px solid #ddd; ul { @@ -157,17 +180,17 @@ li { - padding: 0; + padding: 0; list-style: none outside none; i.dashicons { - color: $fs-logo-green-color; - font-size: 3em; + color: $fs-logo-green-color; + font-size: 3em; vertical-align: middle; - line-height: 30px; - float: left; - margin: 0 0 0 -15px; + line-height: 30px; + float: left; + margin: 0 0 0 -15px; } h3 @@ -184,23 +207,23 @@ ul { list-style: none; - margin: 0; + margin: 0; li { - width: 225px; - height: 225px; - float: left; + width: 225px; + height: 225px; + float: left; margin-bottom: 20px; @include box-sizing(content-box); a { - display: block; - width: 100%; - height: 100%; - border: 1px solid; - @include box-shadow(1px 1px 1px rgba(0,0,0,0.2)); + display: block; + width: 100%; + height: 100%; + border: 1px solid; + @include box-shadow(1px 1px 1px rgba(0, 0, 0, 0.2)); background-size: cover; } @@ -215,41 +238,133 @@ .plugin-information-pricing { - background: #FFFEEC; - margin: -16px; - padding: 20px; - border-bottom: 1px solid #DDD; + $pricing_color: #FFFEEC; + $borders_color: #DDD; + margin: -16px; + // padding: 20px; + border-bottom: 1px solid $borders_color; - h3 + .fs-plan { - margin-top: 0; - } - .button - { - width: 100%; - text-align: center; - font-weight: bold; - text-transform: uppercase; - font-size: 1.1em; - } + h3 + { + margin-top: 0; + padding: 20px; + font-size: 16px; + } - label - { - white-space: nowrap; - } + .nav-tab-wrapper + { + border-bottom: 1px solid $borders_color; - ul.fs-trial-terms - { - font-size: 0.9em; + .nav-tab + { + cursor: pointer; + position: relative; + padding: 0 10px; + font-size: 0.9em; - i { - float: left; - margin: 0 0 0 -15px; + label + { + text-transform: uppercase; + color: green; + background: greenyellow; + position: absolute; + left: -1px; + right: -1px; + bottom: 100%; + border: 1px solid darkgreen; + padding: 2px; + text-align: center; + font-size: 0.9em; + line-height: 1em; + } + + &.nav-tab-active + { + cursor: default; + background: $pricing_color; + border-bottom-color: $pricing_color; + } + } } - li { - margin: 10px 0 0 0; + &.fs-single-cycle + { + h3 + { + background: $pricing_color; + margin: 0; + padding-bottom: 0; + color: #0073aa; + } + + .nav-tab-wrapper, + .fs-billing-frequency + { + display: none; + } + } + + .fs-pricing-body + { + background: $pricing_color; + padding: 20px; + } + + .button + { + width: 100%; + text-align: center; + font-weight: bold; + text-transform: uppercase; + font-size: 1.1em; + } + + label + { + white-space: nowrap; + } + + var { + font-style: normal; + } + + .fs-billing-frequency, + .fs-annual-discount + { + text-align: center; + display: block; + font-weight: bold; + margin-bottom: 10px; + text-transform: uppercase; + background: #F3F3F3; + padding: 2px; + border: 1px solid #ccc; + } + + .fs-annual-discount + { + text-transform: none; + color: green; + background: greenyellow; + } + + ul.fs-trial-terms + { + font-size: 0.9em; + + i + { + float: left; + margin: 0 0 0 -15px; + } + + li + { + margin: 10px 0 0 0; + } } } } @@ -261,12 +376,14 @@ margin: -20px -26px; } - table { - width: 100%; - border-spacing: 0; + table + { + width: 100%; + border-spacing: 0; border-collapse: separate; - thead { + thead + { th { padding: 10px 0; @@ -274,10 +391,10 @@ .fs-price { - color: $fs-logo-green-color; + color: $fs-logo-green-color; font-weight: normal; - display: block; - text-align: center; + display: block; + text-align: center; } } @@ -286,16 +403,16 @@ td { border-top: 1px solid #ccc; - padding: 10px 0; + padding: 10px 0; text-align: center; - width: 100px; - color: $fs-logo-green-color; + width: 100px; + color: $fs-logo-green-color; &:first-child { - text-align: left; - width: auto; - color: inherit; + text-align: left; + width: auto; + color: inherit; padding-left: 26px; } } @@ -311,15 +428,14 @@ .dashicons-yes { - width: 30px; - height: 30px; + width: 30px; + height: 30px; font-size: 30px; } } } -@media screen and (max-width: 961px) -{ +@media screen and (max-width: 961px) { #fs_addons { .fs-cards-list diff --git a/includes/entities/class-fs-pricing.php b/includes/entities/class-fs-pricing.php index b541221b4..fb70b5aef 100755 --- a/includes/entities/class-fs-pricing.php +++ b/includes/entities/class-fs-pricing.php @@ -47,4 +47,95 @@ function __construct( $pricing = false ) { static function get_type() { return 'pricing'; } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function has_monthly() { + return ( is_numeric( $this->monthly_price ) && $this->monthly_price > 0 ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function has_annual() { + return ( is_numeric( $this->annual_price ) && $this->annual_price > 0 ); + } + + /** + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function has_lifetime() { + return ( is_numeric( $this->lifetime_price ) && $this->lifetime_price > 0 ); + } + + /** + * Check if unlimited licenses pricing. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function is_unlimited() { + return is_null( $this->licenses ); + } + + + /** + * Check if pricing has more than one billing cycle. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return bool + */ + function is_multi_cycle() { + $cycles = 0; + if ( $this->has_monthly() ) { + $cycles ++; + } + if ( $this->has_annual() ) { + $cycles ++; + } + if ( $this->has_lifetime() ) { + $cycles ++; + } + + return $cycles > 1; + } + + /** + * Get annual over monthly discount. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return int + */ + function annual_discount_percentage() { + return floor( $this->annual_savings() / ( $this->monthly_price * 12 * ( $this->is_unlimited() ? 1 : $this->licenses ) ) * 100 ); + } + + /** + * Get annual over monthly savings. + * + * @author Vova Feldman (@svovaf) + * @since 1.1.8 + * + * @return float + */ + function annual_savings() { + return ( $this->monthly_price * 12 - $this->annual_price ) * ( $this->is_unlimited() ? 1 : $this->licenses ); + } + } \ No newline at end of file diff --git a/includes/fs-plugin-info-dialog.php b/includes/fs-plugin-info-dialog.php index 0ebd9dd82..cd59e58ed 100755 --- a/includes/fs-plugin-info-dialog.php +++ b/includes/fs-plugin-info-dialog.php @@ -288,8 +288,8 @@ private function get_billing_cycle( FS_Plugin_Plan $plan ) { * @author Vova Feldman (@svovaf) * @since 1.1.7 * - * @param \FS_Plugin_Plan $plan - * @param \FS_Pricing $pricing + * @param FS_Plugin_Plan $plan + * @param FS_Pricing $pricing * * @return float|null|string */ @@ -566,161 +566,292 @@ function install_plugin_information() { ?>
- plans ) ) : ?> -
- plans as $plan) : ?> - + is_paid ) : ?> + plans ) ) : ?> +
+ plans as $plan ) : ?> + + pricing[0] ?> + is_multi_cycle() ?> +

slug ), $plan->title ) ?>

- is_paid ) : ?> -
    - pricing ) && 1 == $plan->pricing[0]->licenses ) : ?> - pricing[0] ?> -
  • - - pricing as $pricing ) : ?> -
- - get_plugin_cta( $api, $plan ) ?> -
- is_paid ) : ?> - has_trial() ) : ?> - get_trial_period( $plan ) ?> -
    -
  • - slug ), $trial_period ) ?> -
  • -
  • - slug ), $trial_period, $this->get_price_tag( $plan, $plan->pricing[0] ) ) ?> -
  • +
    - - + get_plugin_cta( $api, $plan ) ?> +
    + has_trial() ) : ?> + get_trial_period( $plan ) ?> +
      +
    • + slug ), $trial_period ) ?> +
    • +
    • + slug ), $trial_period, '' . $this->get_price_tag( $plan, $plan->pricing[0] ) . '' ) ?> +
    • +
    + +
+
- is_paid) : ?> - plans[0] ?> - get_billing_cycle( $plan ) ?> - - - - - -
-

slug ) ?>

-
    - version ) ) { ?> -
  • version; ?>
  • + + +
    +

    slug ) ?>

    +
      + version ) ) { ?> +
    • version; ?>
    • + author ) ) { + ?> +
    • + author, '_blank' ); ?> +
    • author ) ) { - ?> -
    • - author, '_blank' ); ?> -
    • - last_updated ) ) { - ?> -
    • + if ( ! empty( $api->last_updated ) ) { + ?> +
    • last_updated ) ) ); ?>
    • - requires ) ) { - ?> -
    • - requires ); ?> -
    • - tested ) ) { - ?> -
    • tested; ?> -
    • - downloaded ) ) { - ?> -
    • - downloaded ), number_format_i18n( $api->downloaded ) ); ?> -
    • - slug ) && empty( $api->external ) ) { - ?> -
    • -
    • - homepage ) ) { - ?> -
    • -
    • - donate_link ) && empty( $api->contributors ) ) { - ?> -
    • -
    • - -
    -
    - rating ) ) { ?> -

    - $api->rating, - 'type' => 'percent', - 'number' => $api->num_ratings - ) ); ?> - num_ratings ), number_format_i18n( $api->num_ratings ) ); ?> - ratings ) && array_sum( (array) $api->ratings ) > 0 ) { - foreach ( $api->ratings as $key => $ratecount ) { - // Avoid div-by-zero. - $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0; + requires ) ) { + ?> +
  • + requires ); ?> +
  • + tested ) ) { + ?> +
  • tested; ?> +
  • + downloaded ) ) { ?> -
    +
  • + downloaded ), number_format_i18n( $api->downloaded ) ); ?> +
  • + slug ) && empty( $api->external ) ) { + ?> +
  • +
  • + homepage ) ) { + ?> +
  • +
  • + donate_link ) && empty( $api->contributors ) ) { + ?> +
  • +
  • + +
+
+ rating ) ) { ?> +

+ $api->rating, + 'type' => 'percent', + 'number' => $api->num_ratings + ) ); ?> + num_ratings ), number_format_i18n( $api->num_ratings ) ); ?> + ratings ) && array_sum( (array) $api->ratings ) > 0 ) { + foreach ( $api->ratings as $key => $ratecount ) { + // Avoid div-by-zero. + $_rating = $api->num_ratings ? ( $ratecount / $api->num_ratings ) : 0; + ?> + - +
+ contributors ) ) { - ?> -

-
+ donate_link ) ) { ?> + +
'error', 'id' => md5( microtime() ), - 'message' => __fs( ($api->is_paid ? 'paid-addon-not-deployed' : 'free-addon-not-deployed'), $api->slug ), + 'message' => __fs( ( $api->is_paid ? 'paid-addon-not-deployed' : 'free-addon-not-deployed' ), $api->slug ), ); fs_require_template( 'admin-notice.php', $missing_notice ); } diff --git a/includes/i18n.php b/includes/i18n.php index 167ec3240..2e2d3b05d 100644 --- a/includes/i18n.php +++ b/includes/i18n.php @@ -327,4 +327,15 @@ 'addon-no-license-message' => __( '%s is a premium only add-on. You have to purchase a license first before activating the plugin.', 'freemius' ), 'addon-trial-cancelled-message' => __( '%s free trial was successfully cancelled. Since the add-on is premium only it was automatically deactivated. If you like to use it in the future, you\'ll have to purchase a license.', 'freemius' ), #endregion Add-On Licensing + #region Billing Cycles + 'monthly' => _x( 'Monthly', 'as every month', 'freemius' ), + 'mo' => _x( 'mo', 'as monthly period', 'freemius' ), + 'annual' => _x( 'Annual', 'as once a year', 'freemius' ), + 'annually' => _x( 'Annually', 'as once a year', 'freemius' ), + 'once' => _x( 'Once', 'as once a year', 'freemius' ), + 'year' => _x( 'year', 'as annual period', 'freemius' ), + 'lifetime' => __( 'Lifetime', 'freemius' ), + 'best' => _x( 'Best', 'e.g. the best product', 'freemius' ), + 'billed-x' => _x( 'Billed %s', 'e.g. billed monthly', 'freemius' ), + #endregion Billing Cycles ); From 55a354cf9104fa16cbcc21b9cb0caf3017d89922 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 18 Apr 2016 20:10:27 -0400 Subject: [PATCH 13/19] [admin-notice] [ui] [minor] Fixed admin notice plugin's title label positioning. --- assets/css/admin/common.css | 2 +- assets/scss/admin/common.scss | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/css/admin/common.css b/assets/css/admin/common.css index 2356c15b8..e5715b82a 100644 --- a/assets/css/admin/common.css +++ b/assets/css/admin/common.css @@ -1 +1 @@ -.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,0.3);color:#fff;padding:2px 10px;position:absolute;bottom:-22px;top:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;box-shadow:0px 2px 2px rgba(6,113,6,0.3);opacity:0.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:'\21B3';padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:'\21B2'} +.fs-notice{position:relative}.fs-notice.fs-has-title{margin-bottom:30px !important}.fs-notice.success{color:green}.fs-notice.promotion{border-color:#00a0d2 !important;background-color:#f2fcff !important}.fs-notice .fs-notice-body{margin:.5em 0;padding:2px}.fs-notice .fs-close{cursor:pointer;color:#aaa;float:right}.fs-notice .fs-close:hover{color:#666}.fs-notice .fs-close>*{margin-top:7px;display:inline-block}.fs-notice label.fs-plugin-title{background:rgba(0,0,0,0.3);color:#fff;padding:2px 10px;position:absolute;top:100%;bottom:auto;right:auto;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;left:10px;font-size:12px;font-weight:bold;cursor:auto}.rtl .fs-notice .fs-close{float:left}.fs-secure-notice{position:fixed;top:32px;left:160px;right:0;background:#ebfdeb;padding:10px 20px;color:green;z-index:9999;box-shadow:0px 2px 2px rgba(6,113,6,0.3);opacity:0.95;filter:alpha(opacity=95)}.fs-secure-notice:hover{opacity:1;filter:alpha(opacity=100)}@media screen and (max-width: 960px){.fs-secure-notice{left:36px}}@media screen and (max-width: 782px){.fs-secure-notice{left:0;top:46px;text-align:center}}span.fs-submenu-item.fs-sub:before{content:'\21B3';padding:0 5px}.rtl span.fs-submenu-item.fs-sub:before{content:'\21B2'} diff --git a/assets/scss/admin/common.scss b/assets/scss/admin/common.scss index 3c299eb9d..4a2f75eb0 100755 --- a/assets/scss/admin/common.scss +++ b/assets/scss/admin/common.scss @@ -59,8 +59,8 @@ color: #fff; padding: 2px 10px; position: absolute; - bottom: -22px; - top: auto; + top: 100%; + bottom: auto; right: auto; @include border-radius(0 0 3px 3px); left: 10px; From bccd3028dcd34b62425041af1f017047f8b5ccb2 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 18 Apr 2016 20:14:28 -0400 Subject: [PATCH 14/19] [add-ons] [trial] If add-on has a trial period, added "trial" label next to the pricing. --- includes/i18n.php | 1 + templates/add-ons.php | 39 +++++++++++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/includes/i18n.php b/includes/i18n.php index 2e2d3b05d..7a3760078 100644 --- a/includes/i18n.php +++ b/includes/i18n.php @@ -338,4 +338,5 @@ 'best' => _x( 'Best', 'e.g. the best product', 'freemius' ), 'billed-x' => _x( 'Billed %s', 'e.g. billed monthly', 'freemius' ), #endregion Billing Cycles + 'view-details' => __( 'View details', 'freemius' ), ); diff --git a/templates/add-ons.php b/templates/add-ons.php index 6b52a69f6..a703a021c 100755 --- a/templates/add-ons.php +++ b/templates/add-ons.php @@ -24,29 +24,33 @@ * @var FS_Plugin[] */ $addons = $fs->get_addons(); + + $has_addons = ( is_array( $addons ) && 0 < count( $addons ) ); ?>

get_plugin_name() ) ?>

- +

- -
    + +
      + slug ) ); $price = 0; + $plan = null; $plans_result = $fs->get_api_site_or_plugin_scope()->get( "/addons/{$addon->id}/plans.json" ); if ( ! isset( $plans_result->error ) ) { $plans = $plans_result->plans; if ( is_array( $plans ) && 0 < count( $plans ) ) { - $plan = $plans[0]; + $plan = new FS_Plugin_Plan( $plans[0] ); $pricing_result = $fs->get_api_site_or_plugin_scope()->get( "/addons/{$addon->id}/plans/{$plan->id}/pricing.json" ); if ( ! isset( $pricing_result->error ) ) { // Update plan's pricing. @@ -70,7 +74,7 @@ } } ?> -
    • +
    • ', esc_url( network_admin_url( 'plugin-install.php?tab=plugin-information&parent_plugin_id=' . $fs->get_id() . '&plugin=' . $addon->slug . @@ -94,23 +98,26 @@
      • +
      • title ?>
      • + class="fs-price">has_trial() ? ' - ' . __fs('trial', $slug) : '')) ?>
      • info->short_description ) ? $addon->info->short_description : 'SHORT DESCRIPTION' ?>
      • +
- - + +
- - \ No newline at end of file From b8af8699eb6af79436f2077aa168b0bf1ae36a9a Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 18 Apr 2016 20:15:43 -0400 Subject: [PATCH 15/19] [minor] Slight improvement of the anonymous id calculation method in case that the previously stored id was empty. --- includes/class-freemius.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/includes/class-freemius.php b/includes/class-freemius.php index ac35f749d..ac880e806 100644 --- a/includes/class-freemius.php +++ b/includes/class-freemius.php @@ -1257,18 +1257,25 @@ private function store_connectivity_info( $pong, $is_connected ) { * @return string */ function get_anonymous_id() { - if ( ! self::$_accounts->has_option( 'unique_id' ) ) { + $unique_id = self::$_accounts->get_option( 'unique_id' ); + + if ( empty( $unique_id ) || ! is_string( $unique_id ) ) { $key = get_site_url(); // If localhost, assign microtime instead of domain. - if ( WP_FS__IS_LOCALHOST || false !== strpos( $key, 'localhost' ) ) { + if ( WP_FS__IS_LOCALHOST || + false !== strpos( $key, 'localhost' ) || + false === strpos( $key, '.' ) + ) { $key = microtime(); } - self::$_accounts->set_option( 'unique_id', md5( $key ), true ); + $unique_id = md5( $key ); + + self::$_accounts->set_option( 'unique_id', $unique_id, true ); } - return self::$_accounts->get_option( 'unique_id' ); + return $unique_id; } /** From 9790d745aa54d651c79a33cc7693243340a9bf57 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Mon, 18 Apr 2016 20:17:08 -0400 Subject: [PATCH 16/19] [add-ons] [multi-cycle-pricing] [discount] Forgot to add the "Save " prefix in the annual discount. --- includes/fs-plugin-info-dialog.php | 53 +++++++++++++++--------------- includes/i18n.php | 3 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/includes/fs-plugin-info-dialog.php b/includes/fs-plugin-info-dialog.php index cd59e58ed..2280144a0 100755 --- a/includes/fs-plugin-info-dialog.php +++ b/includes/fs-plugin-info-dialog.php @@ -567,20 +567,20 @@ function install_plugin_information() {
is_paid ) : ?> - plans ) ) : ?> -
- plans as $plan ) : ?> - - pricing[0] ?> - is_multi_cycle() ?> -
-

slug ), $plan->title ) ?>

+ plans ) ) : ?> +
+ plans as $plan ) : ?> + + pricing[0] ?> + is_multi_cycle() ?> +
+

slug ), $plan->title ) ?>

has_annual() ?> has_monthly() ?>
- pricing[0]->annual_discount_percentage() : 0 ?> + pricing[0]->annual_discount_percentage() : 0 ?> 0 ) : ?> - % + slug ), $annual_discount . '%' ) ?>
@@ -766,10 +765,10 @@ function install_plugin_information() {
-
-
- - +
+
+ +

slug ) ?>

diff --git a/includes/i18n.php b/includes/i18n.php index 7a3760078..519aaba31 100644 --- a/includes/i18n.php +++ b/includes/i18n.php @@ -337,6 +337,7 @@ 'lifetime' => __( 'Lifetime', 'freemius' ), 'best' => _x( 'Best', 'e.g. the best product', 'freemius' ), 'billed-x' => _x( 'Billed %s', 'e.g. billed monthly', 'freemius' ), + 'save-x' => _x( 'Save %s', 'as a discount of $5 or 10%', 'freemius' ), #endregion Billing Cycles - 'view-details' => __( 'View details', 'freemius' ), + 'view-details' => __( 'View details', 'freemius' ), ); From 3ec1e7b7bc9e397cc81dbb54c5a8f87b770a2c18 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Tue, 19 Apr 2016 16:40:28 -0400 Subject: [PATCH 17/19] [connectivity-test] [curl] Reordered the params so the anonymous id will be passed as the first query string param. From some reason some cURL requests failed to pass the param when it's last in order. --- includes/class-fs-api.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/includes/class-fs-api.php b/includes/class-fs-api.php index f827ab177..d2a8a08ba 100755 --- a/includes/class-fs-api.php +++ b/includes/class-fs-api.php @@ -363,9 +363,10 @@ function ping( $unique_anonymous_id = null, $params = array() ) { $pong = is_null( $unique_anonymous_id ) ? Freemius_Api::Ping() : - $this->_call( 'ping.json?' . http_build_query( array_merge( $params, array( - 'uid' => $unique_anonymous_id, - ) ) ) ); + $this->_call( 'ping.json?' . http_build_query( array_merge( + array( 'uid' => $unique_anonymous_id ), + $params + ) ) ); if ( $this->is_valid_ping( $pong ) ) { return $pong; @@ -379,9 +380,10 @@ function ping( $unique_anonymous_id = null, $params = array() ) { $pong = is_null( $unique_anonymous_id ) ? Freemius_Api::Ping() : - $this->_call( 'ping.json?' . http_build_query( array_merge( $params, array( - 'uid' => $unique_anonymous_id, - ) ) ) ); + $this->_call( 'ping.json?' . http_build_query( array_merge( + array( 'uid' => $unique_anonymous_id ), + $params + ) ) ); if ( ! $this->is_valid_ping( $pong ) ) { self::$_options->set_option( 'api_force_http', false, true ); From ce40617d0e09120424a07678b478e8f711cf7e1e Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Tue, 19 Apr 2016 16:41:07 -0400 Subject: [PATCH 18/19] [logging] [minor] --- includes/class-freemius.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/class-freemius.php b/includes/class-freemius.php index ac880e806..91c532a77 100644 --- a/includes/class-freemius.php +++ b/includes/class-freemius.php @@ -1275,6 +1275,8 @@ function get_anonymous_id() { self::$_accounts->set_option( 'unique_id', $unique_id, true ); } + $this->_logger->departure( $unique_id ); + return $unique_id; } From a1c1b63554934da7a4f918330b54180082e31fd7 Mon Sep 17 00:00:00 2001 From: Vova Feldman Date: Tue, 19 Apr 2016 16:43:48 -0400 Subject: [PATCH 19/19] [connectivity] [issue-resolve] [email] Added php info to the email sent to freemius when anonymous mode is disabled (for services), when there's a connectivity issue, and the user clicks the button to give us an option to investigate the issue. --- includes/class-freemius.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/includes/class-freemius.php b/includes/class-freemius.php index 91c532a77..ced3ddab5 100644 --- a/includes/class-freemius.php +++ b/includes/class-freemius.php @@ -1551,6 +1551,17 @@ function _email_about_firewall_issue() { ) ); + // Add PHP info for deeper investigation. + ob_start(); + phpinfo(); + $php_info = ob_get_clean(); + $custom_email_sections['php_info'] = array( + 'title' => 'PHP Info', + 'rows' => array( + 'info' => array( $php_info ) + ) + ); + // Send email with technical details to resolve CloudFlare's firewall unnecessary protection. $this->send_email( 'api@freemius.com', // recipient @@ -1722,6 +1733,7 @@ private function get_email_sections() { 'site' => array( 'title' => 'Site', 'rows' => array( + 'unique_id' => array( 'Address', $this->get_anonymous_id() ), 'address' => array( 'Address', site_url() ), 'host' => array( 'HTTP_HOST',