updater.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. <?php
  2. /**
  3. * Updater class.
  4. *
  5. * @since 6.0.0
  6. *
  7. * @package MonsterInsights
  8. * @subpackage Updater
  9. * @author Chris Christoff
  10. */
  11. // Exit if accessed directly
  12. if ( ! defined( 'ABSPATH' ) ) {
  13. exit;
  14. }
  15. class MonsterInsights_Updater {
  16. /**
  17. * Plugin name.
  18. *
  19. * @since 6.0.0
  20. *
  21. * @var bool|string
  22. */
  23. public $plugin_name = false;
  24. /**
  25. * Plugin slug.
  26. *
  27. * @since 6.0.0
  28. *
  29. * @var bool|string
  30. */
  31. public $plugin_slug = false;
  32. /**
  33. * Plugin path.
  34. *
  35. * @since 6.0.0
  36. *
  37. * @var bool|string
  38. */
  39. public $plugin_path = false;
  40. /**
  41. * URL of the plugin.
  42. *
  43. * @since 6.0.0
  44. *
  45. * @var bool|string
  46. */
  47. public $plugin_url = false;
  48. /**
  49. * Remote URL for getting plugin updates.
  50. *
  51. * @since 6.0.0
  52. *
  53. * @var bool|string
  54. */
  55. public $remote_url = false;
  56. /**
  57. * Version number of the plugin.
  58. *
  59. * @since 6.0.0
  60. *
  61. * @var bool|int
  62. */
  63. public $version = false;
  64. /**
  65. * License key for the plugin.
  66. *
  67. * @since 6.0.0
  68. *
  69. * @var bool|string
  70. */
  71. public $key = false;
  72. /**
  73. * Holds the update data returned from the API.
  74. *
  75. * @since 6.0.0
  76. *
  77. * @var bool|object
  78. */
  79. public $update = false;
  80. /**
  81. * Holds the plugin info details for the update.
  82. *
  83. * @since 6.0.0
  84. *
  85. * @var bool|object
  86. */
  87. public $info = false;
  88. /**
  89. * Primary class constructor.
  90. *
  91. * @since 6.0.0
  92. *
  93. * @param array $config Array of updater config args.
  94. */
  95. public function __construct( array $config ) {
  96. // Set class properties.
  97. $accepted_args = array(
  98. 'plugin_name',
  99. 'plugin_slug',
  100. 'plugin_path',
  101. 'plugin_url',
  102. 'remote_url',
  103. 'version',
  104. 'key'
  105. );
  106. foreach ( $accepted_args as $arg ) {
  107. $this->$arg = $config[$arg];
  108. }
  109. // If the user cannot update plugins, stop processing here.
  110. if ( ! current_user_can( 'update_plugins' ) && ( ! defined( 'DOING_CRON' ) || ! DOING_CRON ) ) {
  111. return;
  112. }
  113. // If it's our site, then return so we don't redirect loop.
  114. if ( strpos( site_url(), 'monsterinsights.com' ) !== false ) {
  115. return;
  116. }
  117. // Load the updater hooks and filters.
  118. add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'update_plugins_filter' ) );
  119. add_filter( 'http_request_args', array( $this, 'http_request_args' ), 10, 2 );
  120. add_filter( 'plugins_api', array( $this, 'plugins_api' ), 10, 3 );
  121. // ManageWP premium update filters
  122. //add_filter( 'mwp_premium_update_notification', array( $this, 'premium_update_push' ) );
  123. //add_filter( 'mwp_premium_perform_update', array( $this, 'premium_update' ) );
  124. }
  125. /**
  126. * Infuse plugin update details when WordPress runs its update checker.
  127. *
  128. * @since 6.0.0
  129. *
  130. * @param object $value The WordPress update object.
  131. * @return object $value Amended WordPress update object on success, default if object is empty.
  132. */
  133. public function update_plugins_filter( $value ) {
  134. // If no update object exists, return early.
  135. if ( empty( $value ) ) {
  136. return $value;
  137. }
  138. // Run update check by pinging the external API. If it fails, return the default update object.
  139. if ( ! $this->update ) {
  140. $this->update = $this->perform_remote_request( 'get-plugin-update', array( 'tgm-updater-plugin' => $this->plugin_slug ) );
  141. if ( ! $this->update || ! empty( $this->update->error ) ) {
  142. $this->update = false;
  143. return $value;
  144. }
  145. }
  146. // Infuse the update object with our data if the version from the remote API is newer.
  147. if ( isset( $this->update->new_version ) && version_compare( $this->version, $this->update->new_version, '<' ) ) {
  148. // The $plugin_update object contains new_version, package, slug and last_update keys.
  149. //$this->update->full_slug = $this->plugin_slug;
  150. //$this->update->name = $this->plugin_name;
  151. $this->update->monsterinsights_plugin = true;
  152. $this->update->old_version = $this->version;
  153. $this->update->plugin = $this->plugin_path;
  154. $value->response[$this->plugin_path] = $this->update;
  155. }
  156. // Return the update object.
  157. return $value;
  158. }
  159. /**
  160. * Disables SSL verification to prevent download package failures.
  161. *
  162. * @since 6.0.0
  163. *
  164. * @param array $args Array of request args.
  165. * @param string $url The URL to be pinged.
  166. * @return array $args Amended array of request args.
  167. */
  168. public function http_request_args( $args, $url ) {
  169. // If this is an SSL request and we are performing an upgrade routine, disable SSL verification.
  170. if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'tgm-updater-action=get-plugin-update' ) ) {
  171. $args['sslverify'] = false;
  172. }
  173. return $args;
  174. }
  175. /**
  176. * Filters the plugins_api function to get our own custom plugin information
  177. * from our private repo.
  178. *
  179. * @since 6.0.0
  180. *
  181. * @param object $api The original plugins_api object.
  182. * @param string $action The action sent by plugins_api.
  183. * @param array $args Additional args to send to plugins_api.
  184. * @return object $api New stdClass with plugin information on success, default response on failure.
  185. */
  186. public function plugins_api( $api, $action = '', $args = null ) {
  187. $plugin = ( 'plugin_information' == $action ) && isset( $args->slug ) && ( $this->plugin_slug == $args->slug );
  188. // If our plugin matches the request, set our own plugin data, else return the default response.
  189. if ( $plugin ) {
  190. return $this->set_plugins_api( $api );
  191. } else {
  192. return $api;
  193. }
  194. }
  195. /**
  196. * Pings a remote API to retrieve plugin information for WordPress to display.
  197. *
  198. * @since 6.0.0
  199. *
  200. * @param object $default_api The default API object.
  201. * @return object $api Return custom plugin information to plugins_api.
  202. */
  203. public function set_plugins_api( $default_api ) {
  204. // Perform the remote request to retrieve our plugin information. If it fails, return the default object.
  205. if ( ! $this->info ) {
  206. $this->info = $this->perform_remote_request( 'get-plugin-info', array( 'tgm-updater-plugin' => $this->plugin_slug ) );
  207. if ( ! $this->info || ! empty( $this->info->error ) ) {
  208. $this->info = false;
  209. return $default_api;
  210. }
  211. }
  212. // Create a new stdClass object and populate it with our plugin information.
  213. $api = new stdClass;
  214. $api->name = isset( $this->info->name ) ? $this->info->name : '';
  215. $api->slug = isset( $this->info->slug ) ? $this->info->slug : '';
  216. $api->version = isset( $this->info->version ) ? $this->info->version : '';
  217. $api->author = isset( $this->info->author ) ? $this->info->author : '';
  218. $api->author_profile = isset( $this->info->author_profile ) ? $this->info->author_profile : '';
  219. $api->requires = isset( $this->info->requires ) ? $this->info->requires : '';
  220. $api->tested = isset( $this->info->tested ) ? $this->info->tested : '';
  221. $api->last_updated = isset( $this->info->last_updated ) ? $this->info->last_updated : '';
  222. $api->homepage = isset( $this->info->homepage ) ? $this->info->homepage : '';
  223. $changelog = isset( $this->info->changelog ) ? $this->info->changelog : '';
  224. $description = isset( $this->info->description ) ? $this->info->description : '';
  225. if ( ! empty( $changelog ) ) {
  226. if ( ! empty( $description ) ) {
  227. $api->sections['description'] = $description;
  228. $api->sections['changelog'] = $changelog;
  229. } else {
  230. $api->sections['changelog'] = $changelog;
  231. }
  232. } else if ( ! empty( $description ) ) {
  233. $api->sections['description'] = $description;
  234. } else {
  235. $api->sections = array();
  236. }
  237. $api->download_link = isset( $this->info->download_link ) ? $this->info->download_link : '';
  238. // Return the new API object with our custom data.
  239. return $api;
  240. }
  241. // Integration with ManageWP
  242. public function premium_update_push( $premium_update ) {
  243. if ( ! function_exists( 'get_plugin_data' ) ) {
  244. include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
  245. }
  246. $update = $this->set_plugins_api( array() );
  247. if ( ! empty( $update ) && version_compare( MONSTERINSIGHTS_VERSION, $update->version, '<' ) ) {
  248. $plugin_data = get_plugin_data( $update->slug );
  249. $plugin_data['type'] = 'plugin';
  250. $plugin_data['slug'] = $update->slug;
  251. $plugin_data['new_version'] = $update->version;
  252. $premium_update[] = $plugin_data;
  253. }
  254. return $premium_update;
  255. }
  256. // Integration with ManageWP
  257. public function premium_update( $premium_update ) {
  258. if ( ! function_exists( 'get_plugin_data' ) ) {
  259. include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
  260. }
  261. $update = $this->set_plugins_api( array() );
  262. if ( ! empty( $update ) && version_compare( MONSTERINSIGHTS_VERSION, $update->version, '<' ) ) {
  263. $plugin_data = get_plugin_data( $update->slug );
  264. $plugin_data['type'] = 'plugin';
  265. $plugin_data['slug'] = $update->slug;
  266. $plugin_data['url'] = $update->download_link; // OR provide your own callback function for managing the update
  267. array_push( $premium_update, $plugin_data );
  268. }
  269. return $premium_update;
  270. }
  271. /**
  272. * Queries the remote URL via wp_remote_post and returns a json decoded response.
  273. *
  274. * @since 6.0.0
  275. *
  276. * @param string $action The name of the $_POST action var.
  277. * @param array $body The content to retrieve from the remote URL.
  278. * @param array $headers The headers to send to the remote URL.
  279. * @param string $return_format The format for returning content from the remote URL.
  280. * @return string|bool Json decoded response on success, false on failure.
  281. */
  282. public function perform_remote_request( $action, $body = array(), $headers = array(), $return_format = 'json' ) {
  283. // Build the body of the request.
  284. $body = wp_parse_args(
  285. $body,
  286. array(
  287. 'tgm-updater-action' => $action,
  288. 'tgm-updater-key' => $this->key,
  289. 'tgm-updater-wp-version' => get_bloginfo( 'version' ),
  290. 'tgm-updater-referer' => site_url(),
  291. 'tgm-updater-mi-version' => MONSTERINSIGHTS_VERSION,
  292. 'tgm-updater-is-pro' => monsterinsights_is_pro_version(),
  293. )
  294. );
  295. $body = http_build_query( $body, '', '&' );
  296. // Build the headers of the request.
  297. $headers = wp_parse_args(
  298. $headers,
  299. array(
  300. 'Content-Type' => 'application/x-www-form-urlencoded',
  301. 'Content-Length' => strlen( $body )
  302. )
  303. );
  304. // Setup variable for wp_remote_post.
  305. $post = array(
  306. 'headers' => $headers,
  307. 'body' => $body
  308. );
  309. // Perform the query and retrieve the response.
  310. $response = wp_remote_post( esc_url_raw( $this->remote_url ), $post );
  311. $response_code = wp_remote_retrieve_response_code( $response );
  312. $response_body = wp_remote_retrieve_body( $response );
  313. // Bail out early if there are any errors.
  314. if ( 200 != $response_code || is_wp_error( $response_body ) ) {
  315. return false;
  316. }
  317. // Return the json decoded content.
  318. return json_decode( $response_body );
  319. }
  320. }