├── composer.json ├── README.md ├── composer.lock ├── .gitignore └── review.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "julien731/wp-review-me", 3 | "description": "A lightweight library to help you get more reviews for your WordPress theme/plugin", 4 | "homepage": "https://github.com/julien731/WP-Review-Me", 5 | "license": "GNU GPL", 6 | "authors": [ 7 | { 8 | "name": "Julien Liabeuf", 9 | "email": "julien@liabeuf.fr", 10 | "homepage": "https://julienliabeuf.com", 11 | "role": "Lead Developer" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.5.0", 16 | "julien731/wp-dismissible-notices-handler": "1.*" 17 | }, 18 | "autoload": { 19 | "files": ["review.php"] 20 | } 21 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP Review Me 2 | 3 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/julien731/WP-Review-Me/badges/quality-score.png?b=develop)](https://scrutinizer-ci.com/g/julien731/WP-Review-Me/?branch=develop) 4 | 5 | *In order to comply with the WordPress.org guidelines, the EDD integration has been removed and the WooCommerce integration has been dropped.* 6 | 7 | Are you distributing WordPress themes or plugins on WordPress.org? Then you know how important reviews are. 8 | 9 | The bad thing with reviews is that, while unhappy users love to let the world know, happy users tend to forget reviewing your product. 10 | 11 | How can you get more good reviews? Simply ask your users. 12 | 13 | ![WP Review Me](http://i.imgur.com/9oNGvm2.png) 14 | 15 | ## How It Works 16 | 17 | Once instantiated, the library will leave an initial timestamp in the user's database. 18 | 19 | When the admin is loaded, the current time is compared to the initial timestamp and, when it is time, an admin notice will kindly ask the user to review your product. 20 | 21 | The admin notices can, of course, be dismissed by the user. It uses the [WP Dismissible Notices Handler library](https://github.com/julien731/WP-Dismissible-Notices-Handler) for handling notices. 22 | 23 | #### Installation 24 | 25 | The simplest way to use WP Review Me is to add it as a Composer dependency: 26 | 27 | ``` 28 | composer require julien731/wp-review-me 29 | ``` 30 | 31 | ### Example 32 | 33 | Creating a new review prompt would look like that: 34 | 35 | ``` 36 | new WP_Review_Me( array( 'days_after' => 10, 'type' => 'plugin', 'slug' => 'my-plugin' ) ); 37 | ``` 38 | 39 | This is the simplest way of creating a review prompt. If you want to customize it further, a few advanced parameters are available. 40 | 41 | You can see the documentation on the wiki page: https://github.com/julien731/WP-Review-Me/wiki 42 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "0d1622f036a475e98f58e5527abcf7e0", 8 | "content-hash": "5db72a2a7c0fc85ea28316b1b3a30f5d", 9 | "packages": [ 10 | { 11 | "name": "julien731/wp-dismissible-notices-handler", 12 | "version": "1.0.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/julien731/WP-Dismissible-Notices-Handler.git", 16 | "reference": "85ce1debbdfd4543e5b835dfe6670b9de99ddf6b" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/julien731/WP-Dismissible-Notices-Handler/zipball/85ce1debbdfd4543e5b835dfe6670b9de99ddf6b", 21 | "reference": "85ce1debbdfd4543e5b835dfe6670b9de99ddf6b", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.5.0" 26 | }, 27 | "type": "library", 28 | "autoload": { 29 | "files": [ 30 | "handler.php" 31 | ] 32 | }, 33 | "notification-url": "https://packagist.org/downloads/", 34 | "license": [ 35 | "GNU GPL" 36 | ], 37 | "authors": [ 38 | { 39 | "name": "Julien Liabeuf", 40 | "email": "julien@liabeuf.fr", 41 | "homepage": "https://julienliabeuf.com", 42 | "role": "Lead Developer" 43 | } 44 | ], 45 | "description": "A simple library to handle Ajax-dismissible admin notices for WordPress", 46 | "homepage": "https://github.com/julien731/WP-Dismissible-Notices-Handler", 47 | "time": "2016-04-03 05:12:27" 48 | } 49 | ], 50 | "packages-dev": [], 51 | "aliases": [], 52 | "minimum-stability": "stable", 53 | "stability-flags": [], 54 | "prefer-stable": false, 55 | "prefer-lowest": false, 56 | "platform": { 57 | "php": ">=5.5.0" 58 | }, 59 | "platform-dev": [] 60 | } 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 4 | 5 | *.iml 6 | 7 | ## Directory-based project format: 8 | .idea/ 9 | # if you remove the above rule, at least ignore the following: 10 | 11 | # User-specific stuff: 12 | # .idea/workspace.xml 13 | # .idea/tasks.xml 14 | # .idea/dictionaries 15 | 16 | # Sensitive or high-churn files: 17 | # .idea/dataSources.ids 18 | # .idea/dataSources.xml 19 | # .idea/sqlDataSources.xml 20 | # .idea/dynamic.xml 21 | # .idea/uiDesigner.xml 22 | 23 | # Gradle: 24 | # .idea/gradle.xml 25 | # .idea/libraries 26 | 27 | # Mongo Explorer plugin: 28 | # .idea/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.ipr 32 | *.iws 33 | 34 | ## Plugin-specific files: 35 | 36 | # IntelliJ 37 | /out/ 38 | 39 | # mpeltonen/sbt-idea plugin 40 | .idea_modules/ 41 | 42 | # JIRA plugin 43 | atlassian-ide-plugin.xml 44 | 45 | # Crashlytics plugin (for Android Studio and IntelliJ) 46 | com_crashlytics_export_strings.xml 47 | crashlytics.properties 48 | crashlytics-build.properties 49 | ### Linux template 50 | *~ 51 | 52 | # KDE directory preferences 53 | .directory 54 | 55 | # Linux trash folder which might appear on any partition or disk 56 | .Trash-* 57 | ### SublimeText template 58 | # cache files for sublime text 59 | *.tmlanguage.cache 60 | *.tmPreferences.cache 61 | *.stTheme.cache 62 | 63 | # workspace files are user-specific 64 | *.sublime-workspace 65 | 66 | # project files should be checked into the repository, unless a significant 67 | # proportion of contributors will probably not be using SublimeText 68 | # *.sublime-project 69 | 70 | # sftp configuration file 71 | sftp-config.json 72 | ### Windows template 73 | # Windows image file caches 74 | Thumbs.db 75 | ehthumbs.db 76 | 77 | # Folder config file 78 | Desktop.ini 79 | 80 | # Recycle Bin used on file shares 81 | $RECYCLE.BIN/ 82 | 83 | # Windows Installer files 84 | *.cab 85 | *.msi 86 | *.msm 87 | *.msp 88 | 89 | # Windows shortcuts 90 | *.lnk 91 | 92 | ### Composer template 93 | composer.phar 94 | vendor/ 95 | 96 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 97 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 98 | # composer.lock 99 | 100 | -------------------------------------------------------------------------------- /review.php: -------------------------------------------------------------------------------- 1 | 13 | * 14 | * @package WP Review Me 15 | * @author Julien Liabeuf 16 | * @version 2.0.1 17 | * @license GPL-2.0+ 18 | * @link https://julienliabeuf.com 19 | * @copyright 2016 Julien Liabeuf 20 | */ 21 | 22 | if ( ! class_exists( 'WP_Review_Me' ) ) { 23 | 24 | class WP_Review_Me { 25 | 26 | /** 27 | * Library version 28 | * 29 | * @since 1.0 30 | * @var string 31 | */ 32 | public $version = '2.0.1'; 33 | 34 | /** 35 | * Required version of PHP. 36 | * 37 | * @since 1.0 38 | * @var string 39 | */ 40 | public $php_version_required = '5.5'; 41 | 42 | /** 43 | * Minimum version of WordPress required to use the library 44 | * 45 | * @since 1.0 46 | * @var string 47 | */ 48 | public $wordpress_version_required = '4.2'; 49 | 50 | /** 51 | * Holds the unique identifying key for this particular instance 52 | * 53 | * @since 1.0 54 | * @var string 55 | */ 56 | protected $key; 57 | 58 | /** 59 | * Link unique ID 60 | * 61 | * @since 1.0 62 | * @var string 63 | */ 64 | public $link_id; 65 | 66 | /** 67 | * WP_Review_Me constructor. 68 | * 69 | * @since 1.0 70 | * 71 | * @param array $args Object settings 72 | */ 73 | public function __construct( $args ) { 74 | 75 | $args = wp_parse_args( $args, $this->get_defaults() ); 76 | $this->days = $args['days_after']; 77 | $this->type = $args['type']; 78 | $this->slug = $args['slug']; 79 | $this->rating = $args['rating']; 80 | $this->message = $args['message']; 81 | $this->link_label = $args['link_label']; 82 | $this->cap = $args['cap']; 83 | $this->scope = $args['scope']; 84 | 85 | // Set the unique identifying key for this instance 86 | $this->key = 'wrm_' . substr( md5( plugin_basename( __FILE__ ) ), 0, 20 ); 87 | $this->link_id = 'wrm-review-link-' . $this->key; 88 | 89 | // Instantiate 90 | $this->init(); 91 | 92 | } 93 | 94 | /** 95 | * Get default object settings 96 | * 97 | * @since 1.0 98 | * @return array 99 | */ 100 | protected function get_defaults() { 101 | 102 | $defaults = array( 103 | 'days_after' => 10, 104 | 'type' => '', 105 | 'slug' => '', 106 | 'rating' => 5, 107 | 'message' => sprintf( esc_html__( 'Hey! It's been a little while that you've been using this product. You might not realize it, but user reviews are such a great help to us. We would be so grateful if you could take a minute to leave a review on WordPress.org. Many thanks in advance :)', 'wp-review-me' ) ), 108 | 'link_label' => esc_html__( 'Click here to leave your review', 'wp-review-me' ), 109 | // Parameters used in WP Dismissible Notices Handler 110 | 'cap' => 'administrator', 111 | 'scope' => 'global', 112 | ); 113 | 114 | return $defaults; 115 | 116 | } 117 | 118 | /** 119 | * Initialize the library 120 | * 121 | * @since 1.0 122 | * @return void 123 | */ 124 | private function init() { 125 | 126 | // Make sure WordPress is compatible 127 | if ( ! $this->is_wp_compatible() ) { 128 | $this->spit_error( 129 | sprintf( 130 | esc_html__( 'The library can not be used because your version of WordPress is too old. You need version %s at least.', 'wp-review-me' ), 131 | $this->wordpress_version_required 132 | ) 133 | ); 134 | 135 | return; 136 | } 137 | 138 | // Make sure PHP is compatible 139 | if ( ! $this->is_php_compatible() ) { 140 | $this->spit_error( 141 | sprintf( 142 | esc_html__( 'The library can not be used because your version of PHP is too old. You need version %s at least.', 'wp-review-me' ), 143 | $this->php_version_required 144 | ) 145 | ); 146 | 147 | return; 148 | } 149 | 150 | // Make sure the dependencies are loaded 151 | if ( ! function_exists( 'dnh_register_notice' ) ) { 152 | 153 | $dnh_file = trailingslashit( plugin_dir_path( __FILE__ ) ) . 'vendor/julien731/wp-dismissible-notices-handler/handler.php'; 154 | 155 | if ( file_exists( $dnh_file ) ) { 156 | require( $dnh_file ); 157 | } 158 | 159 | if ( ! function_exists( 'dnh_register_notice' ) ) { 160 | $this->spit_error( 161 | sprintf( 162 | esc_html__( 'Dependencies are missing. Please run a %s.', 'wp-review-me' ), 163 | 'composer install' 164 | ) 165 | ); 166 | 167 | return; 168 | } 169 | } 170 | 171 | add_action( 'admin_footer', array( $this, 'script' ) ); 172 | add_action( 'wp_ajax_wrm_clicked_review', array( $this, 'dismiss_notice' ) ); 173 | 174 | // And let's roll... maybe. 175 | $this->maybe_prompt(); 176 | 177 | } 178 | 179 | /** 180 | * Check if the current WordPress version fits the requirements 181 | * 182 | * @since 1.0 183 | * @return boolean 184 | */ 185 | private function is_wp_compatible() { 186 | 187 | if ( version_compare( get_bloginfo( 'version' ), $this->wordpress_version_required, '<' ) ) { 188 | return false; 189 | } 190 | 191 | return true; 192 | 193 | } 194 | 195 | /** 196 | * Check if the version of PHP is compatible with this library 197 | * 198 | * @since 1.0 199 | * @return boolean 200 | */ 201 | private function is_php_compatible() { 202 | 203 | if ( version_compare( phpversion(), $this->php_version_required, '<' ) ) { 204 | return false; 205 | } 206 | 207 | return true; 208 | 209 | } 210 | 211 | /** 212 | * Spits an error message at the top of the admin screen 213 | * 214 | * @since 1.0 215 | * 216 | * @param string $error Error message to spit 217 | * 218 | * @return void 219 | */ 220 | protected function spit_error( $error ) { 221 | printf( 222 | '
%1$s %2$s
', 223 | esc_html__( 'WP Review Me Error:', 'wp-review-me' ), 224 | wp_kses_post( $error ) 225 | ); 226 | } 227 | 228 | /** 229 | * Check if it is time to ask for a review 230 | * 231 | * @since 1.0 232 | * @return bool 233 | */ 234 | public function is_time() { 235 | 236 | $installed = (int) get_option( $this->key, 0 ); 237 | 238 | if ( 0 === $installed ) { 239 | $this->setup_date(); 240 | $installed = time(); 241 | } 242 | 243 | if ( $installed + ( $this->days * 86400 ) > time() ) { 244 | return false; 245 | } 246 | 247 | return true; 248 | 249 | } 250 | 251 | /** 252 | * Save the current date as the installation date 253 | * 254 | * @since 1.0 255 | * @return void 256 | */ 257 | protected function setup_date() { 258 | update_option( $this->key, time() ); 259 | } 260 | 261 | /** 262 | * Get the review link 263 | * 264 | * @since 1.0 265 | * @return string 266 | */ 267 | protected function get_review_link() { 268 | 269 | $link = 'https://wordpress.org/support/'; 270 | 271 | switch ( $this->type ) { 272 | 273 | case 'theme': 274 | $link .= 'theme/'; 275 | break; 276 | 277 | case 'plugin': 278 | $link .= 'plugin/'; 279 | break; 280 | 281 | } 282 | 283 | $link .= $this->slug . '/reviews'; 284 | $link = add_query_arg( 'rate', $this->rating, $link ); 285 | $link = esc_url( $link . '#new-post' ); 286 | 287 | return $link; 288 | 289 | } 290 | 291 | /** 292 | * Get the complete link tag 293 | * 294 | * @since 1.0 295 | * @return string 296 | */ 297 | protected function get_review_link_tag() { 298 | 299 | $link = $this->get_review_link(); 300 | 301 | return "$this->link_label"; 302 | 303 | } 304 | 305 | /** 306 | * Trigger the notice if it is time to ask for a review 307 | * 308 | * @since 1.0 309 | * @return void 310 | */ 311 | protected function maybe_prompt() { 312 | 313 | if ( ! $this->is_time() ) { 314 | return; 315 | } 316 | 317 | dnh_register_notice( $this->key, 'updated', $this->get_message(), array( 318 | 'scope' => $this->scope, 319 | 'cap' => $this->cap 320 | ) ); 321 | 322 | } 323 | 324 | /** 325 | * Echo the JS script in the admin footer 326 | * 327 | * @since 1.0 328 | * @return void 329 | */ 330 | public function script() { ?> 331 | 332 | 354 | 355 | link_id ) { 378 | echo "not this instance's job"; 379 | die(); 380 | } 381 | 382 | // Get the DNH notice ID ready 383 | $notice_id = DNH()->get_id( str_replace( 'wrm-review-link-', '', $id ) ); 384 | $dismissed = DNH()->dismiss_notice( $notice_id ); 385 | 386 | echo $dismissed; 387 | 388 | /** 389 | * Fires right after the notice has been dismissed. This allows for various integrations to perform additional tasks. 390 | * 391 | * @since 1.0 392 | * 393 | * @param string $id The notice ID 394 | * @param string $notice_id The notice ID as defined by the DNH class 395 | */ 396 | do_action( 'wrm_after_notice_dismissed', $id, $notice_id ); 397 | 398 | // Stop execution here 399 | die(); 400 | 401 | } 402 | 403 | /** 404 | * Get the review prompt message 405 | * 406 | * @since 1.0 407 | * @return string 408 | */ 409 | protected function get_message() { 410 | 411 | $message = $this->message; 412 | $link = $this->get_review_link_tag(); 413 | $message = $message . ' ' . $link; 414 | 415 | return wp_kses_post( $message ); 416 | 417 | } 418 | 419 | } 420 | 421 | } 422 | --------------------------------------------------------------------------------