iThemes Sync - Version 2.0.0

Version Description

  • Initial Release to wordpress.org

=

Download this release

Release Info

Developer ithemes
Plugin Icon 128x128 iThemes Sync
Version 2.0.0
Comparing to
See all releases

Version 2.0.0

Files changed (105) hide show
  1. admin.php +233 -0
  2. api.php +274 -0
  3. api.txt +60 -0
  4. ca/index.php +1 -0
  5. ca/roots.crt +58 -0
  6. class-ithemes-credentials.php +143 -0
  7. class-ithemes-sync-json.php +64 -0
  8. class-ithemes-sync-utf8-encoder.php +343 -0
  9. client-dashboard.php +413 -0
  10. css/admin-notice.css +53 -0
  11. css/client-dashboard.css +147 -0
  12. css/index.php +1 -0
  13. css/settings-page.css +195 -0
  14. css/social-metabox.css +52 -0
  15. duplicator.php +127 -0
  16. functions.php +1011 -0
  17. history.txt +184 -0
  18. images/index.php +1 -0
  19. index.php +1 -0
  20. init.php +54 -0
  21. js/admin-notice.js +25 -0
  22. js/index.php +1 -0
  23. js/settings-page.js +10 -0
  24. js/social-metabox.js +49 -0
  25. lang/index.php +1 -0
  26. lang/ithemes-sync.pot +620 -0
  27. lib/index.php +1 -0
  28. lib/updater/admin.php +215 -0
  29. lib/updater/api.php +301 -0
  30. lib/updater/api.txt +40 -0
  31. lib/updater/ca/index.php +1 -0
  32. lib/updater/ca/roots.crt +58 -0
  33. lib/updater/class-ithemes-credentials.php +143 -0
  34. lib/updater/css/index.php +1 -0
  35. lib/updater/css/settings-page.css +67 -0
  36. lib/updater/functions.php +61 -0
  37. lib/updater/history.txt +38 -0
  38. lib/updater/images/index.php +1 -0
  39. lib/updater/index.php +1 -0
  40. lib/updater/information.php +56 -0
  41. lib/updater/init.php +54 -0
  42. lib/updater/keys.php +165 -0
  43. lib/updater/load.php +54 -0
  44. lib/updater/packages.php +223 -0
  45. lib/updater/server.php +217 -0
  46. lib/updater/settings-page.php +659 -0
  47. lib/updater/settings.php +336 -0
  48. lib/updater/updates.php +166 -0
  49. load-translations.php +21 -0
  50. load.php +188 -0
  51. notice-handler.php +106 -0
  52. notices.php +187 -0
  53. readme.txt +144 -0
  54. request-handler.php +459 -0
  55. server.php +239 -0
  56. settings-page.php +349 -0
  57. settings.php +211 -0
  58. social.php +167 -0
  59. uninstall.php +22 -0
  60. upgrader-skin.php +23 -0
  61. verbs/db-optimization.php +388 -0
  62. verbs/deauthenticate-user.php +36 -0
  63. verbs/do-update.php +326 -0
  64. verbs/get-admin-bar-item-whitelist.php +32 -0
  65. verbs/get-admin-bar-items.php +32 -0
  66. verbs/get-admin-menu.php +22 -0
  67. verbs/get-authentication-token.php +52 -0
  68. verbs/get-comment-details.php +173 -0
  69. verbs/get-dashboard-widgets.php +22 -0
  70. verbs/get-gf-form-entries.php +64 -0
  71. verbs/get-gf-forms.php +25 -0
  72. verbs/get-notices.php +40 -0
  73. verbs/get-options.php +73 -0
  74. verbs/get-php-details.php +30 -0
  75. verbs/get-plugin-details.php +30 -0
  76. verbs/get-post-types.php +29 -0
  77. verbs/get-posts.php +84 -0
  78. verbs/get-role-details.php +40 -0
  79. verbs/get-server-details.php +30 -0
  80. verbs/get-status-elements.php +28 -0
  81. verbs/get-status.php +79 -0
  82. verbs/get-supported-verbs.php +32 -0
  83. verbs/get-sync-settings.php +30 -0
  84. verbs/get-theme-details.php +30 -0
  85. verbs/get-update-details.php +30 -0
  86. verbs/get-updates.php +32 -0
  87. verbs/get-user-details.php +52 -0
  88. verbs/get-wordpress-details.php +30 -0
  89. verbs/get-wordpress-settings.php +248 -0
  90. verbs/get-wordpress-users.php +31 -0
  91. verbs/get-yoast-seo-summary.php +27 -0
  92. verbs/index.php +1 -0
  93. verbs/manage-comments.php +344 -0
  94. verbs/manage-ithemes-licenses.php +111 -0
  95. verbs/manage-options.php +75 -0
  96. verbs/manage-plugins.php +215 -0
  97. verbs/manage-posts.php +136 -0
  98. verbs/manage-reports.php +107 -0
  99. verbs/manage-roles.php +100 -0
  100. verbs/manage-themes.php +189 -0
  101. verbs/manage-users.php +120 -0
  102. verbs/set-admin-bar-item-whitelist.php +40 -0
  103. verbs/update-google-site-verification-token.php +23 -0
  104. verbs/update-show-sync.php +38 -0
  105. verbs/verb.php +30 -0
admin.php ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Set up admin interface.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-02 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2013-11-19 - Chris Jean
12
+ Added the ability for the show_sync option to control who sees the Sync interface and plugin.
13
+ 1.2.0 - 2014-02-14 - Chris Jean
14
+ Added support for ?ithemes-sync-force-display=1 in the admin page to force a hidden Sync plugin to display for that specific user.
15
+ */
16
+
17
+
18
+ require_once( $GLOBALS['ithemes_sync_path'] . '/load-translations.php' );
19
+
20
+ class Ithemes_Sync_Admin {
21
+ private $page_name = 'ithemes-sync';
22
+
23
+ private $page_ref;
24
+
25
+
26
+ public function __construct() {
27
+ add_action( 'init', array( $this, 'init' ) );
28
+ add_action( 'wp_ajax_ithemes_sync_hide_notice', array( $this, 'hide_authenticate_notice' ) );
29
+ }
30
+
31
+ public function modify_plugins_page() {
32
+ add_filter( 'all_plugins', array( $this, 'remove_sync_plugin' ) );
33
+ }
34
+
35
+ public function remove_sync_plugin( $plugins ) {
36
+ unset( $plugins[basename( $GLOBALS['ithemes_sync_path'] ) . '/init.php'] );
37
+
38
+ return $plugins;
39
+ }
40
+
41
+ public function init() {
42
+ require_once( $GLOBALS['ithemes_sync_path'] . '/settings.php' );
43
+
44
+
45
+ $show_sync = $GLOBALS['ithemes-sync-settings']->get_option( 'show_sync' );
46
+
47
+ if ( is_array( $show_sync ) ) {
48
+ $show_sync = in_array( get_current_user_id(), $show_sync );
49
+ }
50
+
51
+ if ( ! $show_sync && current_user_can( 'manage_options' ) ) {
52
+ $user_id = get_current_user_id();
53
+
54
+ if ( isset( $_GET['ithemes-sync-force-display'] ) ) {
55
+ if ( ! empty( $_GET['ithemes-sync-force-display'] ) ) {
56
+ $show_sync = true;
57
+ set_site_transient( "ithemes-sync-force-display-$user_id", true, 600 );
58
+
59
+ add_action( 'all_admin_notices', array( $this, 'show_force_display_notice' ), 0 );
60
+ } else {
61
+ delete_site_transient( "ithemes-sync-force-display-$user_id" );
62
+
63
+ add_action( 'all_admin_notices', array( $this, 'show_force_display_disable_notice' ), 0 );
64
+ }
65
+ } else if ( false !== get_site_transient( "ithemes-sync-force-display-$user_id" ) ) {
66
+ $show_sync = true;
67
+
68
+ add_action( 'all_admin_notices', array( $this, 'show_force_display_notice' ), 0 );
69
+ }
70
+ }
71
+
72
+
73
+ if ( $show_sync ) {
74
+ if ( ! is_multisite() || is_super_admin() ) {
75
+ add_action( 'admin_menu', array( $this, 'add_admin_pages' ) );
76
+ }
77
+
78
+ add_action( 'network_admin_menu', array( $this, 'add_network_admin_pages' ) );
79
+
80
+
81
+ if ( current_user_can( 'manage_options' ) ) {
82
+ if ( ! get_site_option( 'ithemes-sync-authenticated' ) && ( empty( $_GET['page'] ) || ( $this->page_name != $_GET['page'] ) ) && ! get_site_option( 'ithemes_sync_hide_authenticate_notice' ) ) {
83
+ require_once( $GLOBALS['ithemes_sync_path'] . '/functions.php' );
84
+
85
+ $path_url = Ithemes_Sync_Functions::get_url( $GLOBALS['ithemes_sync_path'] );
86
+ wp_enqueue_style( 'ithemes-updater-admin-notice-style', "$path_url/css/admin-notice.css" );
87
+ wp_enqueue_script( 'ithemes-updater-admin-notice-script', "$path_url/js/admin-notice.js", array( 'jquery' ) );
88
+
89
+ add_action( 'all_admin_notices', array( $this, 'show_authenticate_notice' ), 0 );
90
+
91
+ delete_site_transient( 'ithemes-sync-activated' );
92
+ }
93
+ else if ( ! empty( $_GET['activate'] ) && get_site_transient( 'ithemes-sync-activated' ) ) {
94
+ require_once( $GLOBALS['ithemes_sync_path'] . '/functions.php' );
95
+
96
+ $path_url = Ithemes_Sync_Functions::get_url( $GLOBALS['ithemes_sync_path'] );
97
+ wp_enqueue_style( 'ithemes-updater-admin-notice-style', "$path_url/css/admin-notice.css" );
98
+ wp_enqueue_script( 'ithemes-updater-admin-notice-script', "$path_url/js/admin-notice.js", array( 'jquery' ) );
99
+
100
+ add_action( 'all_admin_notices', array( $this, 'show_activate_notice' ), 0 );
101
+
102
+ delete_site_transient( 'ithemes-sync-activated' );
103
+ }
104
+ }
105
+ } else {
106
+ add_action( 'load-plugins.php', array( $this, 'modify_plugins_page' ) );
107
+ }
108
+ }
109
+
110
+ public function show_activate_notice() {
111
+ if ( is_multisite() && is_network_admin() )
112
+ $url = network_admin_url( 'settings.php' ) . "?page={$this->page_name}";
113
+ else
114
+ $url = admin_url( 'options-general.php' ) . "?page={$this->page_name}";
115
+
116
+ ?>
117
+ <div class="updated" id="ithemes-sync-notice">
118
+ <?php printf( __( 'iThemes Sync is active. <a class="ithemes-sync-notice-button" href="%s">Manage Sync</a> <a class="ithemes-sync-notice-dismiss" href="#">×</a>', 'it-l10n-ithemes-sync' ), $url ); ?>
119
+ </div>
120
+ <?php
121
+
122
+ }
123
+
124
+ public function show_authenticate_notice() {
125
+ if ( is_multisite() && is_network_admin() )
126
+ $url = network_admin_url( 'settings.php' ) . "?page={$this->page_name}";
127
+ else
128
+ $url = admin_url( 'options-general.php' ) . "?page={$this->page_name}";
129
+
130
+ ?>
131
+ <div class="updated" id="ithemes-sync-notice">
132
+ <?php printf( __( 'iThemes Sync is almost ready. <a class="ithemes-sync-notice-button" href="%s">Set Up Sync</a> <a class="ithemes-sync-notice-hide" href="#">×</a>', 'it-l10n-ithemes-sync' ), $url ); ?>
133
+ </div>
134
+ <?php
135
+
136
+ }
137
+
138
+ public function show_force_display_notice() {
139
+ $user_id = get_current_user_id();
140
+ $time = get_site_option( "_site_transient_timeout_ithemes-sync-force-display-$user_id" );
141
+ $time_diff = human_time_diff( time(), $time );
142
+
143
+ $url = admin_url( 'index.php?ithemes-sync-force-display=0' );
144
+
145
+ ?>
146
+ <div class="updated">
147
+ <p><?php printf( __( 'iThemes Sync will show for your user for the next %1$s. Click <a href="%2$s">here</a> to hide iThemes Sync again.', 'it-l10n-ithemes-sync' ), $time_diff, $url ); ?></p>
148
+ </div>
149
+ <?php
150
+
151
+ }
152
+
153
+ public function show_force_display_disable_notice() {
154
+
155
+ ?>
156
+ <div class="updated">
157
+ <p><?php _e( 'iThemes Sync is now hidden from your user again.', 'it-l10n-ithemes-sync' ); ?></p>
158
+ </div>
159
+ <?php
160
+
161
+ }
162
+
163
+ public function hide_authenticate_notice() {
164
+ update_site_option( 'ithemes_sync_hide_authenticate_notice', true );
165
+ }
166
+
167
+ public function add_admin_pages() {
168
+ $this->page_ref = add_options_page( __( 'iThemes Sync', 'it-l10n-ithemes-sync' ), __( 'iThemes Sync', 'it-l10n-ithemes-sync' ), 'manage_options', $this->page_name, array( $this, 'settings_index' ) );
169
+
170
+ add_action( "load-{$this->page_ref}", array( $this, 'load_settings_page' ) );
171
+ }
172
+
173
+ public function add_network_admin_pages() {
174
+ $this->page_ref = add_submenu_page( 'settings.php', __( 'iThemes Sync', 'it-l10n-ithemes-sync' ), __( 'iThemes Sync', 'it-l10n-ithemes-sync' ), 'manage_options', $this->page_name, array( $this, 'settings_index' ) );
175
+
176
+ add_action( "load-{$this->page_ref}", array( $this, 'load_settings_page' ) );
177
+ }
178
+
179
+ public function load_settings_page() {
180
+ require_once( $GLOBALS['ithemes_sync_path'] . '/settings.php' );
181
+
182
+ require( $GLOBALS['ithemes_sync_path'] . '/settings-page.php' );
183
+ }
184
+
185
+ public function settings_index() {
186
+ do_action( 'ithemes_sync_settings_page_index' );
187
+ }
188
+
189
+ private function set_package_details() {
190
+ if ( false !== $this->package_details )
191
+ return;
192
+
193
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
194
+ $this->package_details = Ithemes_Updater_Packages::get_local_details();
195
+ }
196
+
197
+ private function set_registration_link() {
198
+ if ( false !== $this->registration_link )
199
+ return;
200
+
201
+ $url = admin_url( 'options-general.php' ) . "?page={$this->page_name}";
202
+ $this->registration_link = sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', $url, __( 'Manage iThemes product licenses to receive automatic upgrade support', 'it-l10n-ithemes-sync' ), __( 'License', 'it-l10n-ithemes-sync' ) );
203
+ }
204
+
205
+ public function filter_plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
206
+ $this->set_package_details();
207
+ $this->set_registration_link();
208
+
209
+ if ( isset( $this->package_details[$plugin_file] ) )
210
+ $actions[] = $this->registration_link;
211
+
212
+ return $actions;
213
+ }
214
+
215
+ public function filter_theme_action_links( $actions, $theme ) {
216
+ $this->set_package_details();
217
+ $this->set_registration_link();
218
+
219
+ if ( is_object( $theme ) )
220
+ $path = basename( $theme->get_stylesheet_directory() ) . '/style.css';
221
+ else if ( is_array( $theme ) && isset( $theme['Stylesheet Dir'] ) )
222
+ $path = $theme['Stylesheet Dir'] . '/style.css';
223
+ else
224
+ $path = '';
225
+
226
+ if ( isset( $this->package_details[$path] ) )
227
+ $actions[] = $this->registration_link;
228
+
229
+ return $actions;
230
+ }
231
+ }
232
+
233
+ new Ithemes_Sync_Admin();
api.php ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Simple API for managing verbs for Sync.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.4.1
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2013-11-18 - Chris Jean
12
+ Updated default verbs.
13
+ 1.2.0 - 2014-01-20 - Chris Jean
14
+ Added: get-status-elements, manage-plugins, and manage-themes.
15
+ Added: is_default_status_element(), get_default_status_elements().
16
+ 1.2.1 - 2014-02-07 - Chris Jean
17
+ When including a verb's file, errors are no longer hidden. This allows for easier detection of verbs with problems.
18
+ Better handling of invalid verbs.
19
+ 1.3.0 - 2014-03-25 - Chris Jean
20
+ Added: get-notices.
21
+ 1.4.0 - 2014-07-01 - Chris Jean
22
+ Added: get-comment-details, get-options, get-role-details, get-user-details, manage-commments, manage-roles, manage-users.
23
+ register() now returns true when a Verb has been successfully registered.
24
+ 1.4.1 - 2014-11-10 - Chris Jean
25
+ Changed init hook priority to 11 in order to avoid issues with some plugin updates not appearing.
26
+ */
27
+
28
+
29
+ class Ithemes_Sync_API {
30
+ private $verbs = array();
31
+
32
+ private $default_verbs = array(
33
+ 'db-optimization' => 'Ithemes_Sync_Verb_DB_Optimization',
34
+ 'deauthenticate-user' => 'Ithemes_Sync_Verb_Deauthenticate_User',
35
+ 'do-update' => 'Ithemes_Sync_Verb_Do_Update',
36
+ 'get-admin-bar-item-whitelist' => 'Ithemes_Sync_Verb_Get_Admin_Bar_Item_Whitelist',
37
+ 'get-admin-bar-items' => 'Ithemes_Sync_Verb_Get_Admin_Bar_Items',
38
+ 'get-admin-menu' => 'Ithemes_Sync_Verb_Get_Admin_Menu',
39
+ 'get-authentication-token' => 'Ithemes_Sync_Verb_Get_Authentication_Token',
40
+ 'get-comment-details' => 'Ithemes_Sync_Verb_Get_Comment_Details',
41
+ 'get-dashboard-widgets' => 'Ithemes_Sync_Verb_Get_Dashboard_Widgets',
42
+ 'get-options' => 'Ithemes_Sync_Verb_Get_Options',
43
+ 'get-php-details' => 'Ithemes_Sync_Verb_Get_PHP_Details',
44
+ 'get-plugin-details' => 'Ithemes_Sync_Verb_Get_Plugin_Details',
45
+ 'get-posts' => 'Ithemes_Sync_Verb_Get_Posts',
46
+ 'get-post-types' => 'Ithemes_Sync_Verb_Get_Post_Types',
47
+ 'get-notices' => 'Ithemes_Sync_Verb_Get_Notices',
48
+ 'get-role-details' => 'Ithemes_Sync_Verb_Get_Role_Details',
49
+ 'get-server-details' => 'Ithemes_Sync_Verb_Get_Server_Details',
50
+ 'get-status' => 'Ithemes_Sync_Verb_Get_Status',
51
+ 'get-status-elements' => 'Ithemes_Sync_Verb_Get_Status_Elements',
52
+ 'get-supported-verbs' => 'Ithemes_Sync_Verb_Get_Supported_Verbs',
53
+ 'get-sync-settings' => 'Ithemes_Sync_Verb_Get_Sync_Settings',
54
+ 'get-theme-details' => 'Ithemes_Sync_Verb_Get_Theme_Details',
55
+ 'get-update-details' => 'Ithemes_Sync_Verb_Get_Update_Details',
56
+ 'get-updates' => 'Ithemes_Sync_Verb_Get_Updates',
57
+ 'get-user-details' => 'Ithemes_Sync_Verb_Get_User_Details',
58
+ 'get-wordpress-details' => 'Ithemes_Sync_Verb_Get_Wordpress_Details',
59
+ 'get-wordpress-settings' => 'Ithemes_Sync_Verb_Get_Wordpress_Settings',
60
+ 'get-wordpress-users' => 'Ithemes_Sync_Verb_Get_Wordpress_Users',
61
+ 'manage-posts' => 'Ithemes_Sync_Verb_Manage_Posts',
62
+ 'manage-comments' => 'Ithemes_Sync_Verb_Manage_Comments',
63
+ 'manage-ithemes-licenses' => 'Ithemes_Sync_Verb_Manage_Ithemes_Licenses',
64
+ 'manage-options' => 'Ithemes_Sync_Verb_Manage_Options',
65
+ 'manage-plugins' => 'Ithemes_Sync_Verb_Manage_Plugins',
66
+ 'manage-reports' => 'Ithemes_Sync_Verb_Manage_Reports',
67
+ 'manage-roles' => 'Ithemes_Sync_Verb_Manage_Roles',
68
+ 'manage-themes' => 'Ithemes_Sync_Verb_Manage_Themes',
69
+ 'manage-users' => 'Ithemes_Sync_Verb_Manage_Users',
70
+ 'set-admin-bar-item-whitelist' => 'Ithemes_Sync_Verb_Set_Admin_Bar_Item_Whitelist',
71
+ 'update-show-sync' => 'Ithemes_Sync_Verb_Update_Show_Sync',
72
+ 'update-google-site-verification-token' => 'Ithemes_Sync_Verb_Update_Google_Site_Verification_Token',
73
+ );
74
+
75
+ public function __construct() {
76
+ $GLOBALS['ithemes-sync-api'] = $this;
77
+
78
+ require_once( $GLOBALS['ithemes_sync_path'] . '/functions.php' );
79
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
80
+
81
+ // Gravity Forms Verbs
82
+ if ( class_exists( 'GFForms' ) ) {
83
+ $this->default_verbs['get-gf-forms'] = 'Ithemes_Sync_Verb_Get_GF_Forms';
84
+ $this->default_verbs['get-gf-form-entries'] = 'Ithemes_Sync_Verb_Get_GF_Form_Entries';
85
+ }
86
+
87
+ // Yoast SEO Verbs
88
+ if ( is_plugin_active( 'wordpress-seo/wp-seo.php' ) ) {
89
+ $this->default_verbs['get-yoast-seo-summary'] = 'Ithemes_Sync_Verb_Get_Yoast_SEO_Summary';
90
+ }
91
+
92
+ add_action( 'init', array( $this, 'init' ), 11 );
93
+ }
94
+
95
+ public function init() {
96
+ require_once( $GLOBALS['ithemes_sync_path'] . "/verbs/verb.php" );
97
+
98
+ foreach ( $this->default_verbs as $name => $class ) {
99
+ $this->register( $name, $class, $GLOBALS['ithemes_sync_path'] . "/verbs/$name.php" );
100
+ }
101
+
102
+ do_action( 'ithemes_sync_register_verbs', $this );
103
+
104
+ do_action( 'ithemes_sync_verbs_registered' );
105
+ }
106
+
107
+ public function register( $name, $class, $file = '' ) {
108
+ if ( isset( $this->verbs[$name] ) ) {
109
+ do_action( 'ithemes-sync-add-log', 'Tried to add a verb name that already exists.', compact( 'name', 'class', 'file' ) );
110
+ return false;
111
+ }
112
+
113
+ $this->verbs[$name] = compact( 'name', 'class', 'file' );
114
+
115
+ return true;
116
+ }
117
+
118
+ public function get_names() {
119
+ return array_keys( $this->verbs );
120
+ }
121
+
122
+ public function get_description( $name ) {
123
+ $class = $this->get_class( $name );
124
+
125
+ if ( false === $class )
126
+ return '';
127
+
128
+
129
+ $vars = get_class_vars( $class );
130
+
131
+ if ( isset( $vars['description'] ) )
132
+ return $vars['description'];
133
+
134
+ return '';
135
+ }
136
+
137
+ public function get_descriptions() {
138
+ $names = $this->get_names();
139
+ $descriptions = array();
140
+
141
+ foreach ( $names as $name )
142
+ $descriptions[$name] = $this->get_description( $name );
143
+
144
+ return $descriptions;
145
+ }
146
+
147
+ public function get_status_element( $name ) {
148
+ $class = $this->get_class( $name );
149
+
150
+ if ( false === $class ) {
151
+ return false;
152
+ }
153
+
154
+
155
+ $vars = get_class_vars( $class );
156
+
157
+ if ( ! empty( $vars['status_element_name'] ) ) {
158
+ return $vars['status_element_name'];
159
+ }
160
+
161
+ return false;
162
+ }
163
+
164
+ public function get_status_elements() {
165
+ $names = $this->get_names();
166
+ $status_elements = array();
167
+
168
+ foreach ( $names as $name ) {
169
+ $status_element = $this->get_status_element( $name );
170
+
171
+ if ( false !== $status_element ) {
172
+ $status_elements[$status_element] = $name;
173
+ }
174
+ }
175
+
176
+ return $status_elements;
177
+ }
178
+
179
+ public function is_default_status_element( $name ) {
180
+ $class = $this->get_class( $name );
181
+
182
+ if ( false === $class ) {
183
+ return false;
184
+ }
185
+
186
+
187
+ $vars = get_class_vars( $class );
188
+
189
+ if ( ! empty( $vars['show_in_status_by_default'] ) ) {
190
+ return $vars['show_in_status_by_default'];
191
+ }
192
+
193
+ return false;
194
+ }
195
+
196
+ public function get_default_status_elements() {
197
+ $names = $this->get_names();
198
+ $default_status_elements = array();
199
+
200
+ foreach ( $names as $name ) {
201
+ if ( $this->is_default_status_element( $name ) ) {
202
+ $default_status_elements[] = $this->get_status_element( $name );
203
+ }
204
+ }
205
+
206
+ return $default_status_elements;
207
+ }
208
+
209
+ public function run( $name, $arguments = array() ) {
210
+ $object = $this->get_object( $name );
211
+
212
+ if ( false == $object ) {
213
+ return new WP_Error( 'invalid-verb-object', "Unable to find a valid object for the requested verb: $name" );
214
+ }
215
+
216
+ return $object->run( $arguments );
217
+ }
218
+
219
+ private function get_class( $name ) {
220
+ if ( ! isset( $this->verbs[$name] ) ) {
221
+ do_action( 'ithemes-sync-add-log', 'Unable to find requested verb.', array( 'name' => $name ) );
222
+ return false;
223
+ }
224
+
225
+ if ( ! class_exists( $this->verbs[$name]['class'] ) ) {
226
+ if ( empty( $this->verbs[$name]['file'] ) ) {
227
+ do_action( 'ithemes-sync-add-log', 'Unable to find requested verb\'s class.', $this->verbs[$name] );
228
+ return false;
229
+ }
230
+ else if ( ! is_file( $this->verbs[$name]['file'] ) ) {
231
+ do_action( 'ithemes-sync-add-log', 'Unable to find requested verb\'s file.', $this->verbs[$name] );
232
+ return false;
233
+ }
234
+ else {
235
+ include_once( $this->verbs[$name]['file'] );
236
+
237
+ if ( ! class_exists( $this->verbs[$name]['class'] ) ) {
238
+ do_action( 'ithemes-sync-add-log', 'Unable to find requested verb\'s class even after loading its file.', $this->verbs[$name] );
239
+ return false;
240
+ }
241
+
242
+ if ( ! is_subclass_of( $this->verbs[$name]['class'], 'Ithemes_Sync_Verb' ) ) {
243
+ do_action( 'ithemes-sync-add-log', 'Verb added without being a subclass of Ithemes_Sync_Verb', $this->verbs[$name] );
244
+ return false;
245
+ }
246
+ }
247
+ }
248
+
249
+ return $this->verbs[$name]['class'];
250
+ }
251
+
252
+ private function get_object( $name ) {
253
+ if ( ! isset( $this->verbs[$name] ) ) {
254
+ do_action( 'ithemes-sync-add-log', 'Unable to find requested verb.', array( 'name' => $name ) );
255
+ return false;
256
+ }
257
+
258
+ if ( ! isset( $this->verbs[$name]['object'] ) ) {
259
+ $class = $this->get_class( $name );
260
+
261
+ if ( false === $class ) {
262
+ return false;
263
+ }
264
+
265
+ $object = new $class();
266
+
267
+ $this->verbs[$name]['object'] = $object;
268
+ }
269
+
270
+ return $this->verbs[$name]['object'];
271
+ }
272
+ }
273
+
274
+ new Ithemes_Sync_API();
api.txt ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #### Notices ####
2
+
3
+ Sync can receive notifications from other projects. These notifications are of two kinds: normal and urgent.
4
+
5
+
6
+ ## Normal Notices ##
7
+
8
+ Normal notices are for providing information that is not urgent but should be made note of when an admin is maintaining the site. They are sent to the server when the server makes status requests or when specifically requesting notices from the site.
9
+
10
+ Example:
11
+
12
+ function backup_plugin_add_notices( $arguments ) {
13
+ $data = array(
14
+ 'last_backup' => 1394859600,
15
+ 'last_backup_diff_from_now' => 988402,
16
+ );
17
+
18
+ // The verbose argument can be checked for in order to provide additional information that may not always be necessary.
19
+ if ( ! empty( $arguments['verbose'] ) ) {
20
+ $data['last_backup_type'] = 'full';
21
+ }
22
+
23
+ ithemes_sync_add_notice( 'backup_plugin', 'no_recent_backups', 'No recent backups', 'It has been more than a week since the last backup.', $data );
24
+
25
+
26
+ // Plugin-specific arguments could be provided to enable additional notices that would be disabled by default.
27
+ if ( ! empty( $arguments['backup_plugin_show_extra_notices'] ) ) {
28
+ ithemes_sync_add_notice( 'backup_plugin', 'superfluous_notice', 'Superfluous notice', 'I ran out of good ideas, so this is merely an example.' );
29
+ // Note how the $data argument is optional in the call above.
30
+ }
31
+ }
32
+ add_action( 'ithemes_sync_add_notices', 'backup_plugin_add_notices' );
33
+
34
+ Details about the ithemes_sync_add_notice function:
35
+ * @param string $source Uniquely identifies the project that is sending the notice.
36
+ * @param string $id Identifies the type of notice. This is to allow the server to differentiate different kinds of notices without having to parse the message.
37
+ * @param string $subject A brief subject description of the notice that is fit for presentation to Sync users.
38
+ * @param string $message A message that is fit for presentation to Sync users.
39
+ * @param array $data Optional. Data that is relevant to the notice. For notices that may be best presented in a graphical manner, this field could be used to send data used to construct the graphic.
40
+ * @return bool True. Currently, it always returns true.
41
+
42
+
43
+ ## Urgent Notices ##
44
+
45
+ Urgent notices are sent immediately to the server so that the administator can be notified directly. These notices are to be used when something important happens on the site that requires immediate attention.
46
+
47
+ Example:
48
+
49
+ function security_plugin_send_urgent_notice() {
50
+ $result = ithemes_sync_send_urgent_notice( 'security_plugin', 'intruder_detected', 'Intruder detected', 'An intruder has been detected on the site.' );
51
+ }
52
+ add_action( 'init', 'security_plugin_send_urgent_notice' );
53
+
54
+ Details about the ithemes_sync_send_urgent_notice function:
55
+ * @param string $source Uniquely identifies the project that is sending the notice.
56
+ * @param string $id Identifies the type of notice. This is to allow the server to differentiate different kinds of notices without having to parse the message.
57
+ * @param string $subject A brief subject description of the notice that is fit for presentation to Sync users.
58
+ * @param string $message A message that is fit for presentation to Sync users.
59
+ * @param array $data Optional. Data that is relevant to the notice. For notices that may be best presented in a graphical manner, this field could be used to send data used to construct the graphic.
60
+ * @return bool|WP_Error True on successful receipt by the server. WP_Error object otherwise. In the event of an error, the notice is queued for sending to the server when it makes a status or notice request.
ca/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
ca/roots.crt ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ##
2
+ ## CA Root Certificate for api.ithemes.com and s3.amazonaws.com
3
+ ##
4
+ ## Certificate data from Mozilla as of: Sat Dec 29 20:03:40 2012
5
+ ##
6
+
7
+ # @(#) $RCSfile: certdata.txt,v $ $Revision: 1.87 $ $Date: 2012/12/29 16:32:45 $
8
+
9
+ Go Daddy Class 2 CA
10
+ ===================
11
+ -----BEGIN CERTIFICATE-----
12
+ MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
13
+ VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
14
+ ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
15
+ A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
16
+ RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
17
+ ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
18
+ 2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
19
+ qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
20
+ YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
21
+ vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
22
+ BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
23
+ atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
24
+ MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
25
+ A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
26
+ PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
27
+ I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
28
+ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
29
+ Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
30
+ vZ8=
31
+ -----END CERTIFICATE-----
32
+
33
+ VeriSign Class 3 Public Primary Certification Authority - G5
34
+ ============================================================
35
+ -----BEGIN CERTIFICATE-----
36
+ MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE
37
+ BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
38
+ ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
39
+ IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp
40
+ ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB
41
+ yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln
42
+ biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh
43
+ dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt
44
+ YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
45
+ ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz
46
+ j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD
47
+ Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
48
+ Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r
49
+ fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/
50
+ BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
51
+ Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
52
+ aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG
53
+ SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+
54
+ X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE
55
+ KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC
56
+ Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE
57
+ ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
58
+ -----END CERTIFICATE-----
class-ithemes-credentials.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //-----------------------------------------------------------------------------
4
+
5
+ class iThemes_Credentials
6
+ {
7
+
8
+ //-----------------------------------------------------------------------------
9
+
10
+ protected $hash = 'sha256';
11
+
12
+ protected $salt_padding = 'wdHVwU&HcYcWnllo%kTUUnxpScy4%ICM29';
13
+
14
+ protected $iteration_count = 131072;
15
+
16
+ protected $key_length = 64;
17
+
18
+ protected $password;
19
+
20
+ //-----------------------------------------------------------------------------
21
+
22
+ public function __construct($username, $password, $options = array())
23
+ {
24
+
25
+ $this->username = $username;
26
+
27
+ $this->password = $password;
28
+
29
+
30
+ if(!empty($options['hash']))
31
+ $this->hash = strtolower(trim($options['hash']));
32
+
33
+ if(!empty($options['iterations']))
34
+ $this->iteration_count = intval($options['iterations']);
35
+
36
+ if(!empty($options['salt']))
37
+ $this->salt_padding = $options['salt'];
38
+
39
+ if(!empty($options['key_length']))
40
+ $this->key_length = intval($options['key_length']);
41
+
42
+ }
43
+
44
+ //-----------------------------------------------------------------------------
45
+
46
+ public static function get_password_hash($username, $password, $options = array())
47
+ {
48
+
49
+ $hasher = new iThemes_Credentials($username, $password, $options);
50
+
51
+ return $hasher->get_pbkdf2();
52
+
53
+ }
54
+
55
+ //-----------------------------------------------------------------------------
56
+
57
+ public function get_salt()
58
+ {
59
+
60
+ return strtolower(trim($this->username)) . $this->salt_padding;
61
+
62
+ }
63
+
64
+ //-----------------------------------------------------------------------------
65
+
66
+ public function get_pbkdf2()
67
+ {
68
+
69
+ return $this->pbkdf2($this->hash,
70
+ $this->password,
71
+ $this->get_salt(),
72
+ $this->iteration_count,
73
+ $this->key_length / 2,
74
+ false);
75
+
76
+ }
77
+
78
+ //-----------------------------------------------------------------------------
79
+
80
+ /*
81
+ * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
82
+ * $algorithm - The hash algorithm to use. Recommended: SHA256
83
+ * $password - The password.
84
+ * $salt - A salt that is unique to the password.
85
+ * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
86
+ * $key_length - The length of the derived key in bytes.
87
+ * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
88
+ * Returns: A $key_length-byte key derived from the password and salt.
89
+ *
90
+ * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
91
+ *
92
+ * This implementation of PBKDF2 was originally created by https://defuse.ca
93
+ * With improvements by http://www.variations-of-shadow.com
94
+ */
95
+ private function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
96
+ {
97
+
98
+ $algorithm = strtolower($algorithm);
99
+
100
+ if(!in_array($algorithm, hash_algos(), true))
101
+ trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
102
+
103
+ if($count <= 0 || $key_length <= 0)
104
+ trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);
105
+
106
+
107
+ $hash_length = strlen(hash($algorithm, '', true));
108
+ $block_count = ceil($key_length / $hash_length);
109
+
110
+ $output = '';
111
+
112
+ for($i = 1; $i <= $block_count; $i++)
113
+ {
114
+
115
+ // $i encoded as 4 bytes, big endian.
116
+ $last = $salt . pack("N", $i);
117
+
118
+ // first iteration
119
+ $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
120
+
121
+ // perform the other $count - 1 iterations
122
+ for ($j = 1; $j < $count; $j++)
123
+ {
124
+ $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
125
+ }
126
+
127
+ $output .= $xorsum;
128
+
129
+ }
130
+
131
+ if($raw_output)
132
+ return substr($output, 0, $key_length);
133
+ else
134
+ return bin2hex(substr($output, 0, $key_length));
135
+
136
+ }
137
+
138
+ //-----------------------------------------------------------------------------
139
+
140
+ }
141
+
142
+ //-----------------------------------------------------------------------------
143
+
class-ithemes-sync-json.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ final class Ithemes_Sync_JSON {
4
+ public static function encode( $data ) {
5
+ $json = json_encode( $data );
6
+
7
+ if ( false !== $json ) {
8
+ return $json;
9
+ }
10
+
11
+ $data = self::get_fixed_data( $data );
12
+ $json = json_encode( $data );
13
+
14
+ if ( false !== $json ) {
15
+ return $json;
16
+ }
17
+
18
+ return json_encode( 'Encoding error. Unable to JSON encode the data due to an unknown error.' );
19
+ }
20
+
21
+ private static function get_fixed_data( $data ) {
22
+ if ( false !== json_encode( $data ) ) {
23
+ return $data;
24
+ }
25
+
26
+ if ( is_string( $data ) ) {
27
+ require_once( $GLOBALS['ithemes_sync_path'] . '/class-ithemes-sync-utf8-encoder.php' );
28
+ $data = Ithemes_Sync_UTF8_Encoder::toUTF8( $data );
29
+
30
+ if ( false === json_encode( $data ) ) {
31
+ return 'INVALID STRING DATA';
32
+ }
33
+
34
+ return $data;
35
+ }
36
+
37
+ if ( is_array( $data ) ) {
38
+ foreach ( $data as $key => $value ) {
39
+ if ( false === json_encode( $key ) ) {
40
+ unset( $data[$key] );
41
+ $key = self::get_fixed_data( $key );
42
+ $data[$key] = $value;
43
+ }
44
+
45
+ if ( false === json_encode( $value ) ) {
46
+ $value = self::get_fixed_data( $value );
47
+ $data[$key] = $value;
48
+ }
49
+ }
50
+
51
+ if ( false === json_encode( $data ) ) {
52
+ return 'INVALID ARRAY DATA';
53
+ }
54
+
55
+ return $data;
56
+ }
57
+
58
+ if ( is_object( $data ) ) {
59
+ return 'INVALID CLASS ' . get_class( $data ) . ' DATA';
60
+ }
61
+
62
+ return 'INVALID DATA (' . gettype( $data ) . ')';
63
+ }
64
+ }
class-ithemes-sync-utf8-encoder.php ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Copyright (c) 2008 Sebastián Grignoli
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions
8
+ are met:
9
+ 1. Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ 2. Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ 3. Neither the name of copyright holders nor the names of its
15
+ contributors may be used to endorse or promote products derived
16
+ from this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
22
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ POSSIBILITY OF SUCH DAMAGE.
29
+ */
30
+
31
+ /**
32
+ * @author "Sebastián Grignoli" <grignoli@gmail.com>
33
+ * @package Encoding
34
+ * @version 2.0
35
+ * @link https://github.com/neitanod/forceutf8
36
+ * @example https://github.com/neitanod/forceutf8
37
+ * @license Revised BSD
38
+ */
39
+
40
+ // Modified by Chris Jean of iThemes.com to add backcompat support for PHP 5.2.4.
41
+
42
+ class Ithemes_Sync_UTF8_Encoder {
43
+
44
+ protected static $win1252ToUtf8 = array(
45
+ 128 => "\xe2\x82\xac",
46
+
47
+ 130 => "\xe2\x80\x9a",
48
+ 131 => "\xc6\x92",
49
+ 132 => "\xe2\x80\x9e",
50
+ 133 => "\xe2\x80\xa6",
51
+ 134 => "\xe2\x80\xa0",
52
+ 135 => "\xe2\x80\xa1",
53
+ 136 => "\xcb\x86",
54
+ 137 => "\xe2\x80\xb0",
55
+ 138 => "\xc5\xa0",
56
+ 139 => "\xe2\x80\xb9",
57
+ 140 => "\xc5\x92",
58
+
59
+ 142 => "\xc5\xbd",
60
+
61
+
62
+ 145 => "\xe2\x80\x98",
63
+ 146 => "\xe2\x80\x99",
64
+ 147 => "\xe2\x80\x9c",
65
+ 148 => "\xe2\x80\x9d",
66
+ 149 => "\xe2\x80\xa2",
67
+ 150 => "\xe2\x80\x93",
68
+ 151 => "\xe2\x80\x94",
69
+ 152 => "\xcb\x9c",
70
+ 153 => "\xe2\x84\xa2",
71
+ 154 => "\xc5\xa1",
72
+ 155 => "\xe2\x80\xba",
73
+ 156 => "\xc5\x93",
74
+
75
+ 158 => "\xc5\xbe",
76
+ 159 => "\xc5\xb8"
77
+ );
78
+
79
+ protected static $brokenUtf8ToUtf8 = array(
80
+ "\xc2\x80" => "\xe2\x82\xac",
81
+
82
+ "\xc2\x82" => "\xe2\x80\x9a",
83
+ "\xc2\x83" => "\xc6\x92",
84
+ "\xc2\x84" => "\xe2\x80\x9e",
85
+ "\xc2\x85" => "\xe2\x80\xa6",
86
+ "\xc2\x86" => "\xe2\x80\xa0",
87
+ "\xc2\x87" => "\xe2\x80\xa1",
88
+ "\xc2\x88" => "\xcb\x86",
89
+ "\xc2\x89" => "\xe2\x80\xb0",
90
+ "\xc2\x8a" => "\xc5\xa0",
91
+ "\xc2\x8b" => "\xe2\x80\xb9",
92
+ "\xc2\x8c" => "\xc5\x92",
93
+
94
+ "\xc2\x8e" => "\xc5\xbd",
95
+
96
+
97
+ "\xc2\x91" => "\xe2\x80\x98",
98
+ "\xc2\x92" => "\xe2\x80\x99",
99
+ "\xc2\x93" => "\xe2\x80\x9c",
100
+ "\xc2\x94" => "\xe2\x80\x9d",
101
+ "\xc2\x95" => "\xe2\x80\xa2",
102
+ "\xc2\x96" => "\xe2\x80\x93",
103
+ "\xc2\x97" => "\xe2\x80\x94",
104
+ "\xc2\x98" => "\xcb\x9c",
105
+ "\xc2\x99" => "\xe2\x84\xa2",
106
+ "\xc2\x9a" => "\xc5\xa1",
107
+ "\xc2\x9b" => "\xe2\x80\xba",
108
+ "\xc2\x9c" => "\xc5\x93",
109
+
110
+ "\xc2\x9e" => "\xc5\xbe",
111
+ "\xc2\x9f" => "\xc5\xb8"
112
+ );
113
+
114
+ protected static $utf8ToWin1252 = array(
115
+ "\xe2\x82\xac" => "\x80",
116
+
117
+ "\xe2\x80\x9a" => "\x82",
118
+ "\xc6\x92" => "\x83",
119
+ "\xe2\x80\x9e" => "\x84",
120
+ "\xe2\x80\xa6" => "\x85",
121
+ "\xe2\x80\xa0" => "\x86",
122
+ "\xe2\x80\xa1" => "\x87",
123
+ "\xcb\x86" => "\x88",
124
+ "\xe2\x80\xb0" => "\x89",
125
+ "\xc5\xa0" => "\x8a",
126
+ "\xe2\x80\xb9" => "\x8b",
127
+ "\xc5\x92" => "\x8c",
128
+
129
+ "\xc5\xbd" => "\x8e",
130
+
131
+
132
+ "\xe2\x80\x98" => "\x91",
133
+ "\xe2\x80\x99" => "\x92",
134
+ "\xe2\x80\x9c" => "\x93",
135
+ "\xe2\x80\x9d" => "\x94",
136
+ "\xe2\x80\xa2" => "\x95",
137
+ "\xe2\x80\x93" => "\x96",
138
+ "\xe2\x80\x94" => "\x97",
139
+ "\xcb\x9c" => "\x98",
140
+ "\xe2\x84\xa2" => "\x99",
141
+ "\xc5\xa1" => "\x9a",
142
+ "\xe2\x80\xba" => "\x9b",
143
+ "\xc5\x93" => "\x9c",
144
+
145
+ "\xc5\xbe" => "\x9e",
146
+ "\xc5\xb8" => "\x9f"
147
+ );
148
+
149
+ static function toUTF8($text){
150
+ /**
151
+ * Function \ForceUTF8\Encoding::toUTF8
152
+ *
153
+ * This function leaves UTF8 characters alone, while converting almost all non-UTF8 to UTF8.
154
+ *
155
+ * It assumes that the encoding of the original string is either Windows-1252 or ISO 8859-1.
156
+ *
157
+ * It may fail to convert characters to UTF-8 if they fall into one of these scenarios:
158
+ *
159
+ * 1) when any of these characters: ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß
160
+ * are followed by any of these: ("group B")
161
+ * ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶•¸¹º»¼½¾¿
162
+ * For example: %ABREPRESENT%C9%BB. «REPRESENTÉ»
163
+ * The "«" (%AB) character will be converted, but the "É" followed by "»" (%C9%BB)
164
+ * is also a valid unicode character, and will be left unchanged.
165
+ *
166
+ * 2) when any of these: àáâãäåæçèéêëìíîï are followed by TWO chars from group B,
167
+ * 3) when any of these: ðñòó are followed by THREE chars from group B.
168
+ *
169
+ * @name toUTF8
170
+ * @param string $text Any string.
171
+ * @return string The same string, UTF8 encoded
172
+ *
173
+ */
174
+
175
+ if(is_array($text))
176
+ {
177
+ foreach($text as $k => $v)
178
+ {
179
+ $text[$k] = self::toUTF8($v);
180
+ }
181
+ return $text;
182
+ }
183
+
184
+ if(!is_string($text)) {
185
+ return $text;
186
+ }
187
+
188
+ $max = self::strlen($text);
189
+
190
+ $buf = "";
191
+ for($i = 0; $i < $max; $i++){
192
+ $c1 = $text{$i};
193
+ if($c1>="\xc0"){ //Should be converted to UTF8, if it's not UTF8 already
194
+ $c2 = $i+1 >= $max? "\x00" : $text{$i+1};
195
+ $c3 = $i+2 >= $max? "\x00" : $text{$i+2};
196
+ $c4 = $i+3 >= $max? "\x00" : $text{$i+3};
197
+ if($c1 >= "\xc0" & $c1 <= "\xdf"){ //looks like 2 bytes UTF8
198
+ if($c2 >= "\x80" && $c2 <= "\xbf"){ //yeah, almost sure it's UTF8 already
199
+ $buf .= $c1 . $c2;
200
+ $i++;
201
+ } else { //not valid UTF8. Convert it.
202
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");
203
+ $cc2 = ($c1 & "\x3f") | "\x80";
204
+ $buf .= $cc1 . $cc2;
205
+ }
206
+ } elseif($c1 >= "\xe0" & $c1 <= "\xef"){ //looks like 3 bytes UTF8
207
+ if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf"){ //yeah, almost sure it's UTF8 already
208
+ $buf .= $c1 . $c2 . $c3;
209
+ $i = $i + 2;
210
+ } else { //not valid UTF8. Convert it.
211
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");
212
+ $cc2 = ($c1 & "\x3f") | "\x80";
213
+ $buf .= $cc1 . $cc2;
214
+ }
215
+ } elseif($c1 >= "\xf0" & $c1 <= "\xf7"){ //looks like 4 bytes UTF8
216
+ if($c2 >= "\x80" && $c2 <= "\xbf" && $c3 >= "\x80" && $c3 <= "\xbf" && $c4 >= "\x80" && $c4 <= "\xbf"){ //yeah, almost sure it's UTF8 already
217
+ $buf .= $c1 . $c2 . $c3 . $c4;
218
+ $i = $i + 3;
219
+ } else { //not valid UTF8. Convert it.
220
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");
221
+ $cc2 = ($c1 & "\x3f") | "\x80";
222
+ $buf .= $cc1 . $cc2;
223
+ }
224
+ } else { //doesn't look like UTF8, but should be converted
225
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");
226
+ $cc2 = (($c1 & "\x3f") | "\x80");
227
+ $buf .= $cc1 . $cc2;
228
+ }
229
+ } elseif(($c1 & "\xc0") == "\x80"){ // needs conversion
230
+ if(isset(self::$win1252ToUtf8[ord($c1)])) { //found in Windows-1252 special cases
231
+ $buf .= self::$win1252ToUtf8[ord($c1)];
232
+ } else {
233
+ $cc1 = (chr(ord($c1) / 64) | "\xc0");
234
+ $cc2 = (($c1 & "\x3f") | "\x80");
235
+ $buf .= $cc1 . $cc2;
236
+ }
237
+ } else { // it doesn't need conversion
238
+ $buf .= $c1;
239
+ }
240
+ }
241
+ return $buf;
242
+ }
243
+
244
+ static function toWin1252($text, $option = '') {
245
+ if(is_array($text)) {
246
+ foreach($text as $k => $v) {
247
+ $text[$k] = self::toWin1252($v, $option);
248
+ }
249
+ return $text;
250
+ } elseif(is_string($text)) {
251
+ return static::utf8_decode($text, $option);
252
+ } else {
253
+ return $text;
254
+ }
255
+ }
256
+
257
+ static function toISO8859($text) {
258
+ return self::toWin1252($text);
259
+ }
260
+
261
+ static function toLatin1($text) {
262
+ return self::toWin1252($text);
263
+ }
264
+
265
+ static function fixUTF8($text, $option = ''){
266
+ if(is_array($text)) {
267
+ foreach($text as $k => $v) {
268
+ $text[$k] = self::fixUTF8($v, $option);
269
+ }
270
+ return $text;
271
+ }
272
+
273
+ $last = "";
274
+ while($last <> $text){
275
+ $last = $text;
276
+ $text = self::toUTF8(static::utf8_decode($text, $option));
277
+ }
278
+ $text = self::toUTF8(static::utf8_decode($text, $option));
279
+ return $text;
280
+ }
281
+
282
+ static function UTF8FixWin1252Chars($text){
283
+ // If you received an UTF-8 string that was converted from Windows-1252 as it was ISO8859-1
284
+ // (ignoring Windows-1252 chars from 80 to 9F) use this function to fix it.
285
+ // See: http://en.wikipedia.org/wiki/Windows-1252
286
+
287
+ return str_replace(array_keys(self::$brokenUtf8ToUtf8), array_values(self::$brokenUtf8ToUtf8), $text);
288
+ }
289
+
290
+ static function removeBOM($str=""){
291
+ if(substr($str, 0,3) == pack("CCC",0xef,0xbb,0xbf)) {
292
+ $str=substr($str, 3);
293
+ }
294
+ return $str;
295
+ }
296
+
297
+ protected static function strlen($text){
298
+ return (function_exists('mb_strlen') && ((int) ini_get('mbstring.func_overload')) & 2) ?
299
+ mb_strlen($text,'8bit') : strlen($text);
300
+ }
301
+
302
+ public static function normalizeEncoding($encodingLabel)
303
+ {
304
+ $encoding = strtoupper($encodingLabel);
305
+ $encoding = preg_replace('/[^a-zA-Z0-9\s]/', '', $encoding);
306
+ $equivalences = array(
307
+ 'ISO88591' => 'ISO-8859-1',
308
+ 'ISO8859' => 'ISO-8859-1',
309
+ 'ISO' => 'ISO-8859-1',
310
+ 'LATIN1' => 'ISO-8859-1',
311
+ 'LATIN' => 'ISO-8859-1',
312
+ 'UTF8' => 'UTF-8',
313
+ 'UTF' => 'UTF-8',
314
+ 'WIN1252' => 'ISO-8859-1',
315
+ 'WINDOWS1252' => 'ISO-8859-1'
316
+ );
317
+
318
+ if(empty($equivalences[$encoding])){
319
+ return 'UTF-8';
320
+ }
321
+
322
+ return $equivalences[$encoding];
323
+ }
324
+
325
+ public static function encode($encodingLabel, $text)
326
+ {
327
+ $encodingLabel = self::normalizeEncoding($encodingLabel);
328
+ if($encodingLabel == 'ISO-8859-1') return self::toLatin1($text);
329
+ return self::toUTF8($text);
330
+ }
331
+
332
+ protected static function utf8_decode($text, $option)
333
+ {
334
+ if ($option == '' || !function_exists('iconv')) {
335
+ $o = utf8_decode(
336
+ str_replace(array_keys(self::$utf8ToWin1252), array_values(self::$utf8ToWin1252), self::toUTF8($text))
337
+ );
338
+ } else {
339
+ $o = iconv("UTF-8", "Windows-1252" . ($option == 'TRANSLIT' ? '//TRANSLIT' : ($option == 'IGNORE' ? '//IGNORE' : '')), $text);
340
+ }
341
+ return $o;
342
+ }
343
+ }
client-dashboard.php ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Ithemes_Sync_Client_Dashboard {
4
+
5
+ /**
6
+ * @var array List of item IDs to ignore from our list and never send to Sync
7
+ */
8
+ private $_admin_bar_ignored_items = array(
9
+ 'menu-toggle', //This is for admin responsiveness
10
+ );
11
+
12
+ public function __construct() {
13
+ add_action( 'init', array( $this, 'init' ) );
14
+ }
15
+
16
+ public function init() {
17
+ // If this user is supposed to see the client dashboard
18
+ if ( get_user_meta( get_current_user_id(), 'ithemes-sync-client-dashboard', true ) ) {
19
+ if ( ! get_user_meta( get_current_user_id(), 'ithemes-sync-client-dashboard-no-css', true ) ) {
20
+ // Enqueue our admin scripts and styles
21
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_dashboard_scripts' ) );
22
+ }
23
+
24
+ if ( get_user_meta( get_current_user_id(), 'ithemes-sync-suppress-admin-notices', true ) ) {
25
+ add_action( 'network_admin_notices', array( $this, 'admin_notices_start' ), 1 );
26
+ add_action( 'user_admin_notices', array( $this, 'admin_notices_start' ), 1 );
27
+ add_action( 'admin_notices', array( $this, 'admin_notices_start' ), 1 );
28
+ add_action( 'all_admin_notices', array( $this, 'admin_notices_start' ), 1 );
29
+ add_action( 'network_admin_notices', array( $this, 'admin_notices_end' ), 999 );
30
+ add_action( 'user_admin_notices', array( $this, 'admin_notices_end' ), 999 );
31
+ add_action( 'admin_notices', array( $this, 'admin_notices_end' ), 999 );
32
+ add_action( 'all_admin_notices', array( $this, 'admin_notices_end' ), 999 );
33
+ }
34
+
35
+ // Filter menu items
36
+ add_action( 'admin_menu', array( $this, 'filter_admin_menu' ), 999999 ); //We want to be last!
37
+
38
+ // Filter admin bar items
39
+ add_action( 'wp_before_admin_bar_render', array( $this, 'filter_admin_bar_menu' ), 1002 );
40
+
41
+ // Filter dashboard widgets - screen_layout_columns fires late but before dashboard widgets are run or referenced in screen options
42
+ add_action( 'screen_layout_columns', array( $this, 'filter_dashboard_widgets' ) );
43
+
44
+ // Filter welcome panel
45
+ add_filter( 'show_welcome_panel', array( $this, 'show_welcome_panel' ) );
46
+ }
47
+
48
+ add_action( 'admin_menu', array( $this, 'admin_menu' ), 999999 ); //We want to be last!
49
+ add_action( 'wp_before_admin_bar_render', array( $this, 'admin_bar_menu' ), 999 );
50
+
51
+ add_action( 'switch_theme', array( $this, 'clear_cache' ) );
52
+ add_action( 'activate_plugin', array( $this, 'clear_cache' ) );
53
+ add_action( 'deactivate_plugin', array( $this, 'clear_cache' ) );
54
+ add_action( 'update_option_active_plugins', array( $this, 'clear_cache' ) );
55
+ add_action( 'add_option_active_plugins', array( $this, 'clear_cache' ) );
56
+
57
+ add_action( 'update_site_option_active_sitewide_plugins', array( $this, 'clear_cache_network' ) );
58
+ add_action( 'add_site_option_active_sitewide_plugins', array( $this, 'clear_cache_network' ) );
59
+
60
+ /**
61
+ * Handle Dashboard Widgets
62
+ */
63
+ add_action( 'admin_footer-index.php', array( $this, 'dashboard_admin_footer' ) );
64
+ }
65
+
66
+ public function enqueue_dashboard_scripts() {
67
+ // Enqueue an additional CSS file on the admin dashboard
68
+ wp_enqueue_style( 'ithemes-sync-client-dashboard', plugins_url( 'css/client-dashboard.css', __FILE__ ), array( 'wp-admin' ), '20140801' );
69
+ }
70
+
71
+ /**
72
+ * @param int User Id
73
+ *
74
+ * @return array List of menu_slugs to allow in menu. index is top level item, value is array of submenu items (or true for 'all')
75
+ */
76
+ public function get_allowed_admin_menu_items( $user_id = null ) {
77
+ if ( ! $user_id ) {
78
+ $user_id = get_current_user_id();
79
+ }
80
+
81
+ $menu_whitelist = get_user_meta( $user_id, 'ithemes-sync-admin_menu-whitelist', true );
82
+
83
+ // If the user's admin_menu whitelist doesn't exist, set it.
84
+ if ( ! is_array( $menu_whitelist ) ) {
85
+ $menu_whitelist = array(
86
+ 'index.php' => array(),
87
+ 'edit.php' => true,
88
+ 'edit.php?post_type=page' => true,
89
+ 'edit-comments.php' => array(),
90
+ );
91
+ update_user_meta( $user_id, 'ithemes-sync-admin_menu-whitelist', $menu_whitelist );
92
+ }
93
+
94
+ return apply_filters( 'ithemes-sync-menu-items-whitelist', $menu_whitelist );
95
+ }
96
+
97
+ public function filter_admin_menu() {
98
+ /**
99
+ * @var array An array of menu elements. Each element is an array containing:
100
+ * [0] - Label
101
+ * [1] = capability
102
+ * [2] = menu_slug
103
+ * [3] = page_title
104
+ * [4] = classes
105
+ * [5] = hookname
106
+ * [6] = icon_url
107
+ */
108
+ global $menu;
109
+
110
+ /**
111
+ * @var array An array of arrays of submenu elements, indexed by the menu_slug of the main menu item. Each submenu element is an array containing:
112
+ * [0] - Label
113
+ * [1] = capability
114
+ * [2] = menu_slug
115
+ */
116
+ global $submenu;
117
+
118
+ /**
119
+ * @var array List of menu_slugs to allow in menu. index is top level item, value is array of submenu items (or true for 'all')
120
+ */
121
+ $menu_whitelist = $this->get_allowed_admin_menu_items();
122
+
123
+ foreach ( $menu as $pos => $menu_item ) {
124
+ if ( 'wp-menu-separator' == $menu_item[4] ) {
125
+ continue;
126
+ }
127
+ if ( ! in_array( $menu_item[2], array_keys( $menu_whitelist ) ) ) {
128
+ if ( isset( $submenu[ $menu_item[2] ] ) ) {
129
+ unset( $submenu[ $menu_item[2] ] );
130
+ }
131
+ unset( $menu[ $pos ] );
132
+ } elseif ( true !== $menu_whitelist[ $menu_item[2] ] && isset( $submenu[ $menu_item[2] ] ) ) {
133
+ foreach ( $submenu[ $menu_item[2] ] as $subpos => $submenu_item ) {
134
+ if ( ! in_array( $submenu_item[2], $menu_whitelist[ $menu_item[2] ] ) ) {
135
+ unset( $submenu[ $menu_item[2] ][ $subpos ] );
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ public function admin_menu() {
143
+ $admin_menu = get_option( 'ithemes-sync-admin_menu' );
144
+ if ( false === $admin_menu ) {
145
+ /**
146
+ * @var array An array of menu elements. Each element is an array containing:
147
+ * [0] - Label
148
+ * [1] = capability
149
+ * [2] = menu_slug
150
+ * [3] = page_title
151
+ * [4] = classes
152
+ * [5] = hookname
153
+ * [6] = icon_url
154
+ */
155
+ global $menu;
156
+
157
+ /**
158
+ * @var array An array of arrays of submenu elements, indexed by the menu_slug of the main menu item. Each submenu element is an array containing:
159
+ * [0] - Label
160
+ * [1] = capability
161
+ * [2] = menu_slug
162
+ */
163
+ global $submenu;
164
+
165
+ $admin_menu = array();
166
+ foreach ( $menu as $menu_item ) {
167
+ $admin_menu[$menu_item[2]] = array();
168
+ $admin_menu[$menu_item[2]]['label'] = $menu_item[0];
169
+
170
+ if ( ! empty( $submenu[$menu_item[2]] ) ) {
171
+ $admin_menu[$menu_item[2]]['children'] = array();
172
+ foreach ( $submenu[$menu_item[2]] as $submenu_item ) {
173
+ $admin_menu[$menu_item[2]]['children'][$submenu_item[2]] = array( 'label' => $submenu_item[0] );
174
+ }
175
+ }
176
+ }
177
+
178
+ update_option( 'ithemes-sync-admin_menu', $admin_menu );
179
+ }
180
+ }
181
+
182
+ /**
183
+ * @param int User Id
184
+ *
185
+ * @return array List of dashboard widget ids to allow
186
+ */
187
+ public function get_allowed_admin_bar_items( $user_id = null ) {
188
+ if ( ! $user_id ) {
189
+ $user_id = get_current_user_id();
190
+ }
191
+
192
+ $whitelist = get_user_meta( $user_id, 'ithemes-sync-admin-bar-item-whitelist-' . get_current_blog_id(), true );
193
+
194
+ // If the user's admin_menu whitelist doesn't exist, set it.
195
+ if ( ! is_array( $whitelist ) ) {
196
+ $whitelist = array(
197
+ 'wp-logo',
198
+ 'about',
199
+ 'wp-logo-external',
200
+ 'wporg',
201
+ 'documentation',
202
+ 'support-forums',
203
+ 'feedback',
204
+ 'site-name',
205
+ 'comments',
206
+ 'new-content',
207
+ 'new-post',
208
+ 'new-page',
209
+ 'top-secondary',
210
+ 'my-account',
211
+ 'user-actions',
212
+ 'user-info',
213
+ 'edit-profile',
214
+ 'logout',
215
+ );
216
+ update_user_meta( $user_id, 'ithemes-sync-admin-bar-item-whitelist-' . get_current_blog_id(), $whitelist );
217
+ }
218
+
219
+ $whitelist = array_merge( $whitelist, $this->_admin_bar_ignored_items );
220
+
221
+ return apply_filters( 'ithemes-sync-admin-bar-item-whitelist-' . get_current_blog_id(), $whitelist );
222
+ }
223
+
224
+ public function filter_admin_bar_menu() {
225
+ global $wp_admin_bar;
226
+ $whitelist = $this->get_allowed_admin_bar_items();
227
+
228
+ foreach ( $wp_admin_bar->get_nodes() as $node ) {
229
+ if ( ! in_array( $node->id, $whitelist ) ) {
230
+ $wp_admin_bar->remove_node( $node->id );
231
+ }
232
+ }
233
+ }
234
+
235
+ public function admin_bar_menu() {
236
+ global $wp_admin_bar;
237
+ $meta_key = 'ithemes-sync-admin-bar-items-' . get_current_blog_id();
238
+
239
+ if ( is_array( get_user_meta( get_current_user_id(), $meta_key, true ) ) ) {
240
+ return true;
241
+ }
242
+
243
+ $admin_bar = array();
244
+ $admin_bar_nodes = $wp_admin_bar->get_nodes();
245
+
246
+ $last_count = null;
247
+ $iterations = 0;
248
+ while ( ! empty( $admin_bar_nodes ) && ++$iterations <= 100 ) {
249
+ foreach ( $admin_bar_nodes as $key => $current_node ) {
250
+ if ( in_array( $current_node->id, $this->_admin_bar_ignored_items ) ) {
251
+ // Don't send ignored items
252
+ unset( $admin_bar_nodes[ $key ] );
253
+ } elseif ( false == $current_node->parent ) {
254
+ // Process parents
255
+ $admin_bar[ $current_node->id ] = $this->_create_admin_bar_node( $current_node );
256
+ unset( $admin_bar_nodes[ $key ] );
257
+ } elseif ( $this->_place_child( $admin_bar, $this->_create_admin_bar_node( $current_node ) ) ) {
258
+ // If we placed a child node successfully, remove it
259
+ unset( $admin_bar_nodes[ $key ] );
260
+ }
261
+ }
262
+
263
+ /**
264
+ * If we haven't parsed any elements out since the last time through
265
+ * the loop, break out to avoid an infinite loop. This only happens
266
+ * when there are mal-formed admin bar nodes (such as nodes with a
267
+ * non-existent parent)
268
+ */
269
+ if ( $last_count == count( $admin_bar_nodes ) ) {
270
+ break;
271
+ }
272
+ $last_count = count( $admin_bar_nodes );
273
+ }
274
+
275
+ update_user_meta( get_current_user_id(), $meta_key, $admin_bar );
276
+ }
277
+
278
+ private function _place_child( &$all_nodes, $child_node ) {
279
+ foreach ( $all_nodes as $id => &$node ) {
280
+ if ( $id == $child_node->parent ) {
281
+ $node->children[ $child_node->id ] = $child_node;
282
+ return true;
283
+ } elseif ( ! empty( $node->children ) && $this->_place_child( $node->children, $child_node ) ) {
284
+ return true;
285
+ }
286
+ }
287
+ return false;
288
+ }
289
+
290
+ private function _create_admin_bar_node( $node_info ) {
291
+ $node = new stdClass();
292
+ $node->id = $node_info->id;
293
+ $node->title = $node_info->title;
294
+ $node->parent = $node_info->parent;
295
+ $node->type = $node_info->group? 'group':'item';
296
+ $node->children = array();
297
+ return $node;
298
+ }
299
+
300
+ /**
301
+ * Clear the cache of our admin_menu and dashboard metaboxes by deleting the option
302
+ */
303
+ public function clear_cache() {
304
+ delete_option( 'ithemes-sync-admin_menu' );
305
+ delete_option( 'ithemes-sync-dashboard-metaboxes' );
306
+
307
+ $users = get_users( array( 'blog_id' => get_current_blog_id(), 'fields' => array( 'ID' ) ) );
308
+ $meta_key = 'ithemes-sync-admin-bar-items-' . get_current_blog_id();
309
+ foreach ( $users as $user ) {
310
+ delete_user_meta( $user->ID, $meta_key );
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Clear the cache of our admin_menu and dashboard metaboxes from the whole
316
+ * network (site...ugh) by deleting the option from each site (blog...ugh again)
317
+ */
318
+ public function clear_cache_network() {
319
+ // Get the current site and only clear options from blogs in this site
320
+ $site = get_current_site(); // Site = Network? Ugh.
321
+ if ( $site && isset( $site->id ) ) {
322
+ global $wpdb;
323
+ $query = $wpdb->prepare( "SELECT `blog_id` FROM $wpdb->blogs WHERE `site_id`=%d", absint( $site->id ) );
324
+ $users = get_users( array( 'blog_id' => 0, 'fields' => array( 'ID' ) ) );
325
+ foreach( $wpdb->get_col( $query ) as $blog_id ) {
326
+ delete_blog_option( $blog_id, 'ithemes-sync-admin_menu' );
327
+ delete_blog_option( $blog_id, 'ithemes-sync-dashboard-metaboxes' );
328
+
329
+ $meta_key = 'ithemes-sync-admin-bar-items-' . $blog_id;
330
+ foreach ( $users as $user ) {
331
+ delete_user_meta( $user->ID, $meta_key );
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ public function dashboard_admin_footer() {
338
+ $meta_box_list = get_option( 'ithemes-sync-dashboard-metaboxes' );
339
+ if ( false === $meta_box_list ) {
340
+ global $wp_meta_boxes;
341
+ $screen = get_current_screen();
342
+ $meta_box_list = array();
343
+ foreach ( $wp_meta_boxes[$screen->id] as $box_position ) {
344
+ foreach ( $box_position as $box_set ) {
345
+ foreach ( $box_set as $box ) {
346
+ $meta_box_list[ $box['id'] ] = $box['title'];
347
+ }
348
+ }
349
+ }
350
+ $meta_box_list['show_welcome_panel'] = _x( 'Welcome', 'Welcome panel' );
351
+ update_option( 'ithemes-sync-dashboard-metaboxes', $meta_box_list );
352
+ }
353
+ }
354
+
355
+ /**
356
+ * @param int User Id
357
+ *
358
+ * @return array List of dashboard widget ids to allow
359
+ */
360
+ public function get_allowed_dashboard_widgets( $user_id = null ) {
361
+ if ( ! $user_id ) {
362
+ $user_id = get_current_user_id();
363
+ }
364
+
365
+ $whitelist = get_user_meta( $user_id, 'ithemes-sync-dashboard-widget-whitelist', true );
366
+
367
+ // If the user's admin_menu whitelist doesn't exist, set it.
368
+ if ( ! is_array( $whitelist ) ) {
369
+ $whitelist = array(
370
+ 'dashboard_right_now',
371
+ 'dashboard_quick_press',
372
+ 'show_welcome_panel',
373
+ );
374
+ update_user_meta( $user_id, 'ithemes-sync-dashboard-widget-whitelist', $whitelist );
375
+ }
376
+
377
+ return apply_filters( 'ithemes-sync-dashboard-widget-whitelist', $whitelist );
378
+ }
379
+
380
+ public function filter_dashboard_widgets() {
381
+ $screen = get_current_screen();
382
+ if ( $screen && 'dashboard' == $screen->id ) {
383
+ global $wp_meta_boxes;
384
+ $whitelist = $this->get_allowed_dashboard_widgets();
385
+
386
+ foreach ( $wp_meta_boxes[$screen->id] as $box_position => $box_position_boxes ) {
387
+ foreach ( $box_position_boxes as $priority => $box_set ) {
388
+ foreach ( $box_set as $index => $box ) {
389
+ if ( ! in_array( $box['id'], $whitelist ) ) {
390
+ unset( $wp_meta_boxes[$screen->id][$box_position][$priority][$index] );
391
+ }
392
+ }
393
+ }
394
+ }
395
+ }
396
+ }
397
+
398
+ public function show_welcome_panel( $show ) {
399
+ if ( ! in_array( 'show_welcome_panel', $this->get_allowed_dashboard_widgets() ) ) {
400
+ $show = 0;
401
+ }
402
+ return $show;
403
+ }
404
+
405
+ public function admin_notices_start() {
406
+ ob_start();
407
+ }
408
+
409
+ public function admin_notices_end() {
410
+ ob_end_clean();
411
+ }
412
+ }
413
+ new Ithemes_Sync_Client_Dashboard();
css/admin-notice.css ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ithemes-sync-notice {
2
+ background: #F3FCE6 url('../images/sync-icon.png') .85em center no-repeat;
3
+ border: 1px solid #D8E9C1;
4
+ border-bottom-width: 2px;
5
+ border-radius: 3px;
6
+ color: #44654E;
7
+ font-size: 14px;
8
+ line-height: 1.6;
9
+ margin: 1em 0;
10
+ padding: 1em .75em 1em 5em;
11
+ position: relative;
12
+ }
13
+
14
+ #ithemes-sync-notice a {
15
+ color: #495D68;
16
+ font-weight: bold;
17
+ text-decoration: none;
18
+ }
19
+
20
+ #ithemes-sync-notice .ithemes-sync-notice-button {
21
+ background: #D8E9C1;
22
+ border: 1px solid #B7CC9B;
23
+ border-bottom-width: 2px;
24
+ border-radius: 3px;
25
+ display: inline-block;
26
+ margin: -8px 0;
27
+ padding: 6px 14px;
28
+ -webkit-transition: all .1s linear;
29
+ -moz-transition: all .1s linear;
30
+ transition: all .1s linear;
31
+ }
32
+ #ithemes-sync-notice .ithemes-sync-notice-button:hover {
33
+ background: #F9FFF0;
34
+ }
35
+
36
+ #ithemes-sync-notice .ithemes-sync-notice-dismiss,
37
+ #ithemes-sync-notice .ithemes-sync-notice-hide {
38
+ border: 1px solid transparent;
39
+ border-radius: 3px;
40
+ display: inline-block;
41
+ float: right;
42
+ margin: -8px 0;
43
+ padding: 6px 14px;
44
+ -webkit-transition: all .1s linear;
45
+ -moz-transition: all .1s linear;
46
+ transition: all .1s linear;
47
+ }
48
+ #ithemes-sync-notice .ithemes-sync-notice-dismiss:hover,
49
+ #ithemes-sync-notice .ithemes-sync-notice-hide:hover {
50
+ background: #D8E9C1;
51
+ border: 1px solid #B7CC9B;
52
+ border-bottom-width: 2px;
53
+ }
css/client-dashboard.css ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Admin bar and left menu adjustments
3
+ */
4
+
5
+ #wpadminbar {
6
+ height: 42px;
7
+ }
8
+ html.wp-toolbar {
9
+ padding-top: 42px;
10
+ }
11
+ #wpadminbar .quicklinks .ab-empty-item,
12
+ #wpadminbar .quicklinks a,
13
+ #wpadminbar .shortlink-input {
14
+ height: 42px;
15
+ }
16
+ #wpadminbar .quicklinks a,
17
+ #wpadminbar #wp-admin-bar-user-info span {
18
+ font-size: 16px;
19
+ font-weight: 300;
20
+ line-height: 40px;
21
+ }
22
+ #wpadminbar #wp-admin-bar-user-info span {
23
+ height: 26px;
24
+ }
25
+ #wpadminbar .ab-icon, #wpadminbar .ab-item:before, #wpadminbar>#wp-toolbar>#wp-admin-bar-root-default .ab-icon {
26
+ font-size: 26px;
27
+ line-height: 1.1em;
28
+ }
29
+ #wpadminbar .quicklinks > ul > li#wp-admin-bar-my-account > a {
30
+ padding: 0 14px;
31
+ }
32
+ #wpadminbar .quicklinks li#wp-admin-bar-my-account.with-avatar>a img {
33
+ width: 26px;
34
+ height: 26px;
35
+ margin-left: 12px;
36
+ }
37
+ #adminmenu,
38
+ #adminmenu .wp-submenu,
39
+ #adminmenuback,
40
+ #adminmenuwrap {
41
+ width: 200px;
42
+ }
43
+ #adminmenu {
44
+ margin: 0;
45
+ }
46
+ #wpcontent, #wpfooter {
47
+ margin-left: 200px;
48
+ }
49
+ #adminmenu .wp-submenu {
50
+ left: 200px;
51
+ }
52
+ #adminmenu .wp-submenu a {
53
+ font-size: 14px;
54
+ font-weight: 300;
55
+ }
56
+ #adminmenu li.wp-menu-separator {
57
+ height: 0;
58
+ margin: 0;
59
+ }
60
+ #adminmenu .wp-not-current-submenu .wp-submenu,
61
+ .folded #adminmenu .wp-has-current-submenu .wp-submenu {
62
+ min-width: 200px;
63
+ }
64
+ #adminmenu .wp-submenu-head,
65
+ #adminmenu a.menu-top {
66
+ font-size: 18px;
67
+ font-weight: 300;
68
+ line-height: 30px;
69
+ min-height: 50px;
70
+ }
71
+ #adminmenu .wp-submenu-head {
72
+ min-height: 34px;
73
+ }
74
+ div.wp-menu-image:before {
75
+ padding: 14px 0;
76
+ }
77
+ #adminmenu li.wp-has-submenu.wp-not-current-submenu:hover:after {
78
+ top: 16px;
79
+ }
80
+
81
+ #adminmenu .awaiting-mod,
82
+ #adminmenu .update-plugins,
83
+ #sidemenu li a span.update-plugins {
84
+ margin: 7px 0 0 14px;
85
+ font-size: 12px;
86
+ font-weight: 300;
87
+ line-height: 19px;
88
+ }
89
+
90
+ /**
91
+ * Screen wpbody-content area
92
+ */
93
+
94
+ .wrap {
95
+ margin-top: 5px;
96
+ }
97
+ #poststuff h3,
98
+ .metabox-holder h3 {
99
+ padding: 14px 18px;
100
+ font-size: 16px;
101
+ }
102
+ .js .meta-box-sortables .postbox .handlediv:before,
103
+ .js .sidebar-name .sidebar-name-arrow:before {
104
+ right: 18px;
105
+ padding: 15px 12px;
106
+ }
107
+ .tablenav {
108
+ height: 48px;
109
+ }
110
+
111
+ /**
112
+ * Buttons and form fields
113
+ */
114
+
115
+ .wp-core-ui .button,
116
+ .wp-core-ui .button-primary,
117
+ .wp-core-ui .button-secondary {
118
+ font-size: 15px;
119
+ height: 40px;
120
+ line-height: 38px;
121
+ padding: 0px 20px 2px;
122
+ }
123
+ .wp-core-ui .button-group.button-large .button, .wp-core-ui .button.button-large {
124
+ height: 40px;
125
+ line-height: 38px;
126
+ padding: 0px 20px 2px;
127
+ }
128
+ .wp-media-buttons span.wp-media-buttons-icon {
129
+ line-height: 1em;
130
+ }
131
+ .wp-admin select {
132
+ line-height: 42px;
133
+ height: 42px;
134
+ }
135
+ .search-box input[name="s"],
136
+ .tablenav .search-plugins input[name="s"],
137
+ .tagsdiv .newtag {
138
+ height: 42px;
139
+ }
140
+ .wp-editor-tabs .wp-switch-editor {
141
+ height: 27px;
142
+ padding: 12px 12px 4px;
143
+ }
144
+ .html-active .wp-editor-tabs .switch-html,
145
+ .tmce-active .wp-editor-tabs .switch-tmce {
146
+ height: 28px;
147
+ }
css/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
css/settings-page.css ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('http://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,400,300,600,700');
2
+
3
+
4
+ .ithemes-sync-wrapper {
5
+ font-family: 'Open Sans', helvetica, arial, sans-serif;
6
+ font-size: 14px;
7
+ line-height: 1.5;
8
+ margin: 0;
9
+ padding: 2em;
10
+ width: 100%;
11
+ max-width: 900px;
12
+ }
13
+
14
+ .ithemes-sync-wrapper p {
15
+ margin: 1em 0;
16
+ }
17
+
18
+ .ithemes-sync-wrapper h2,
19
+ .ithemes-sync-wrapper h3,
20
+ .ithemes-sync-wrapper h4 {
21
+ color: #555555;
22
+ font-weight: 300;
23
+ margin: 1em 0;
24
+ }
25
+
26
+ .ithemes-sync-wrapper h2 {
27
+ font-family: 'Open Sans', helvetica, arial, sans-serif;
28
+ font-size: 2em;
29
+ line-height: 1.5;
30
+ margin: .5em 0;
31
+ padding: 0;
32
+ }
33
+
34
+ .ithemes-sync-wrapper h3 {
35
+ font-size: 1.5em;
36
+ margin: .5em 0;
37
+ }
38
+
39
+
40
+ .ithemes-sync-wrapper .message {
41
+ border: none;
42
+ border-radius: 0;
43
+ -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.04);
44
+ box-shadow: 0 1px 1px rgba(0,0,0,.04);
45
+ margin: 0 0 2em;
46
+ padding: 1em 2em;
47
+ }
48
+ .ithemes-sync-wrapper .success {
49
+ background: #EBF6D7;
50
+ border: 1px solid #86a94a;
51
+ }
52
+
53
+ .ithemes-sync-wrapper .error {
54
+ background: #FFEBE8;
55
+ border: 1px solid #daa69e;
56
+ }
57
+
58
+ .ithemes-sync-manage-users,
59
+ .ithemes-sync-authorize {
60
+ background: #FFF;
61
+ border: 1px solid #E5E5E5;
62
+ -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.04);
63
+ box-shadow: 0 1px 1px rgba(0,0,0,.04);
64
+ margin:2em 0;
65
+ padding: 1em 2em;
66
+ }
67
+
68
+ .ithemes-sync-button {
69
+ background: #a8d657;
70
+ border: none;
71
+ border-bottom: 3px solid #86a94a;
72
+ -webkit-border-radius: 3px;
73
+ -moz-border-radius: 3px;
74
+ border-radius: 3px;
75
+ clear: both;
76
+ color: #13350a;
77
+ cursor: pointer;
78
+ display: inline-block;
79
+ font-size: 1em;
80
+ font-weight: 700;
81
+ margin-top: 1em;
82
+ outline: none;
83
+ padding: .75em 3em;
84
+ text-align: center;
85
+ text-decoration: none;
86
+ }
87
+
88
+ .ithemes-sync-button:hover {
89
+ background: #B8E170;
90
+ color: #13350a;
91
+ }
92
+
93
+ #ithemes-sync-authenticate {
94
+ margin: 1em 0;
95
+ }
96
+ #ithemes-sync-authenticate input {
97
+ border: 1px solid #C7C7C7;
98
+ -webkit-border-radius: 3px;
99
+ -moz-border-radius: 3px;
100
+ margin: 1em 0 .5em 0;
101
+ }
102
+ #ithemes-sync-authenticate input[type="text"],
103
+ #ithemes-sync-authenticate input[type="password"] {
104
+ border: 1px solid #C7C7C7;
105
+ display: block;
106
+ font-size: 1.2em;
107
+ margin-top: 0;
108
+ padding: .5em .75em;
109
+ }
110
+
111
+ #ithemes-sync-authenticate input[type="submit"] {
112
+ background: #a8d657;
113
+ border: none;
114
+ border-bottom: 3px solid #86a94a;
115
+ clear: both;
116
+ color: #13350a;
117
+ cursor: pointer;
118
+ display: block;
119
+ font-size: 1em;
120
+ font-weight: 700;
121
+ margin-top: 2em;
122
+ outline: none;
123
+ padding: .75em 3em;
124
+ }
125
+ #ithemes-sync-authenticate input[type="submit"]:hover {
126
+ background: #B8E170;
127
+ }
128
+
129
+ #ithemes-sync-authenticate input[type="submit"]:active {
130
+ border-bottom: 3px solid #B8E170;
131
+ -webkit-box-shadow: inset 0px 2px 3px #86A94A;
132
+ -moz-box-shadow: inset 2px 3px #86A94A;
133
+ box-shadow: inset 2px 3px #86A94A
134
+ }
135
+ #ithemes-sync-authenticate label {
136
+ font-size: .875em;
137
+ font-weight: 700;
138
+ padding-left: 5px;
139
+ }
140
+
141
+ .ithemes-sync-users {
142
+ border: 1px solid #ececec;
143
+ margin-bottom: 1em;
144
+ padding: 1em 1.5em 2em 1.5em;
145
+ }
146
+ .ithemes-sync-users + .ithemes-sync-users {
147
+ margin-top: 2em;
148
+ }
149
+
150
+ .ithemes-sync-valid-users {
151
+ border-left: 10px solid #A8D657;
152
+ }
153
+
154
+ .ithemes-sync-invalid-users {
155
+ border-left: 10px solid #E86A6A;
156
+ }
157
+
158
+ .ithemes-sync-users h4 {
159
+ font-size: 1.125em;
160
+ padding-left: .75em;
161
+ }
162
+
163
+ .ithemes-sync-users ul {
164
+ border: 1px solid #ebebeb;
165
+ -webkit-border-radius: 5px;
166
+ -moz-border-radius: 5px;
167
+ border-radius: 5px;
168
+ margin: 0;
169
+ }
170
+
171
+ .ithemes-sync-users li {
172
+ border-bottom: 1px solid #ebebeb;
173
+ margin: 0;
174
+ overflow: hidden;
175
+ padding: .5em 1em;
176
+ }
177
+ .ithemes-sync-users ul li:hover {
178
+ background: #f5f5f5;
179
+ }
180
+
181
+ .ithemes-sync-users li .user {
182
+ color: #666;
183
+ font-weight: 700;
184
+ float: left;
185
+ max-width: 50%;
186
+ }
187
+
188
+ .ithemes-sync-users li .deauthenticate {
189
+ float: right;
190
+ }
191
+
192
+ .ithemes-sync-users li .deauthenticate a {
193
+ color: #e86a6a;
194
+ text-decoration: none;
195
+ }
css/social-metabox.css ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ithemes-social {
2
+ margin: 1em 0;
3
+ }
4
+ #ithemes-social .ithemes-social-header {
5
+ border-bottom: 1px solid #ddd;
6
+ overflow:hidden;
7
+ padding: 0 0 5px 0;
8
+ }
9
+ #ithemes-social .ithemes-social-header h3 {
10
+ margin: 0 0 5px 0;
11
+ padding: 0;
12
+ float:left;
13
+ }
14
+ #ithemes-social .ithemes-social-header a {
15
+ float:right;
16
+ }
17
+ #ithemes-social .ithemes-social-accounts {
18
+ margin: 0;
19
+ }
20
+ #ithemes-social .ithemes-social-accounts li{
21
+ border-bottom: 1px solid #e9e9e9;
22
+ padding: 5px 0;
23
+ margin: 0;
24
+ word-break: break-all;
25
+ }
26
+ #ithemes-social .ithemes-social-edit-content {
27
+ cursor:pointer;
28
+ float: right;
29
+ }
30
+ #ithemes-social .ithemes-social-account-content {
31
+ overflow: hidden;
32
+ margin: 0 0 .5em 0;
33
+ }
34
+ #ithemes-social .ithemes-social-account-content textarea {
35
+ margin: 1em 0 .25em 0;
36
+ }
37
+ #ithemes-social .ithemes-social-template-tags {
38
+ font-size: 11px;
39
+ line-height: 1.1;
40
+ float:left;
41
+ }
42
+ #ithemes-social .ithemes-social-char-count {
43
+ float:right;
44
+ }
45
+ #ithemes-social .ithemes-social-char-count.over {
46
+ color: red;
47
+ }
48
+ #ithemes-social .it-social-notice {
49
+ background: #f1f1f1;
50
+ padding: 5px;
51
+ margin: 5px 0;
52
+ }
duplicator.php ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Generates and returns the Duplicate Product URL
5
+ *
6
+ * @since 0.0.0
7
+ *
8
+ * @params object $post Current post object
9
+ * @return string
10
+ */
11
+ function ithemes_sync_duplicate_post_addon_get_duplicating_url( $post ) {
12
+ $args = array(
13
+ 'post_type' => $post->post_type,
14
+ 'ithemes-sync-duplicate-post-id' => $post->ID,
15
+ );
16
+
17
+ return add_query_arg( $args, admin_url( 'post-new.php' ) );
18
+ }
19
+
20
+ /**
21
+ * Add Duplicate action from Any Post Types
22
+ *
23
+ * @since 0.0.0
24
+ *
25
+ * @params array $actions Current post row actions
26
+ * @params object $post Current post object
27
+ * @return array
28
+ */
29
+ function ithemes_sync_duplicate_post_addon_add_duplicate_post_function( $actions, $post ) {
30
+ $url = ithemes_sync_duplicate_post_addon_get_duplicating_url( $post );
31
+ $actions['ithemes_sync_duplicate'] = '<a class="sync_duplicate_post" id="sync-post-' . $post->ID . '" title="' . __( 'Duplicate Post', 'it-l10n-ithemes-sync' ) . '" href="' . $url . '">Duplicate</a>';
32
+
33
+ return $actions;
34
+ }
35
+ add_filter( 'post_row_actions', 'ithemes_sync_duplicate_post_addon_add_duplicate_post_function', 10, 2 );
36
+ add_filter( 'page_row_actions', 'ithemes_sync_duplicate_post_addon_add_duplicate_post_function', 10, 2 );
37
+
38
+ /**
39
+ * Copies previous post content to new post
40
+ *
41
+ * @since 0.0.0
42
+ *
43
+ * @params string $post_content Current WordPress default post content
44
+ * @params object $post Current post object
45
+ * @return array
46
+ */
47
+ function ithemes_sync_duplicate_post_addon_default_post_content( $post_content, $post ) {
48
+ if ( !empty( $_REQUEST['ithemes-sync-duplicate-post-id'] )
49
+ && $duplicate_post_post_id = $_REQUEST['ithemes-sync-duplicate-post-id'] ) {
50
+ $duplicate_post_post = get_post( $duplicate_post_post_id );
51
+ $post_content = $duplicate_post_post->post_content;
52
+ }
53
+
54
+ return $post_content;
55
+ }
56
+ add_filter( 'default_content', 'ithemes_sync_duplicate_post_addon_default_post_content', 10, 2 );
57
+
58
+ /**
59
+ * Copies previous post title to new post
60
+ *
61
+ * @since 1.1.2
62
+ *
63
+ * @params string $post_content Current WordPress default post title
64
+ * @params object $post Current post object
65
+ * @return array
66
+ */
67
+ function ithemes_sync_duplicate_post_addon_default_post_title( $post_title, $post ) {
68
+ if ( !empty( $_REQUEST['ithemes-sync-duplicate-post-id'] )
69
+ && $duplicate_post_post_id = $_REQUEST['ithemes-sync-duplicate-post-id'] ) {
70
+ $duplicate_post_post = get_post( $duplicate_post_post_id );
71
+ $post_title = $duplicate_post_post->post_title . ' - ' . __( 'copy', 'it-l10n-ithemes-exchange' );
72
+ }
73
+
74
+ return $post_title;
75
+ }
76
+ add_filter( 'default_title', 'ithemes_sync_duplicate_post_addon_default_post_title', 10, 2 );
77
+
78
+ /**
79
+ * Copies previous post content to new excerpt (not really used in Exchange)
80
+ *
81
+ * @since 1.1.2
82
+ *
83
+ * @params string $post_content Current WordPress default post excerpt
84
+ * @params object $post Current post object
85
+ * @return array
86
+ */
87
+ function ithemes_sync_duplicate_post_addon_default_post_excerpt( $post_excerpt, $post ) {
88
+ if ( !empty( $_REQUEST['ithemes-sync-duplicate-post-id'] )
89
+ && $duplicate_post_post_id = $_REQUEST['ithemes-sync-duplicate-post-id'] ) {
90
+ $duplicate_post_post = get_post( $duplicate_post_post_id );
91
+ $post_excerpt = $duplicate_post_post->post_excerpt;
92
+ }
93
+
94
+ return $post_excerpt;
95
+ }
96
+ add_filter( 'default_excerpt', 'ithemes_sync_duplicate_post_addon_default_post_excerpt', 10, 2 );
97
+
98
+ /**
99
+ * Copies previous post meta to new post
100
+ *
101
+ * @since 1.1.2
102
+ *
103
+ * @params string $post_type Current WordPress default post type (ignored)
104
+ * @params object $post Current post object
105
+ * @return array
106
+ */
107
+ function ithemes_sync_duplicate_post_addon_default_post_meta( $post_type, $post ) {
108
+ if ( !empty( $_REQUEST['ithemes-sync-duplicate-post-id'] )
109
+ && $duplicate_post_post_id = $_REQUEST['ithemes-sync-duplicate-post-id'] ) {
110
+ $duplicate_post_post_meta = get_post_meta( $duplicate_post_post_id );
111
+ foreach ( $duplicate_post_post_meta as $key => $values ) {
112
+ foreach ( $values as $value ) {
113
+ //We do not want to copy ALL of the post meta, some of it is specific to transaction history, etc.
114
+ if ( in_array( $key, apply_filters( 'ithemes_sync_duplicate_post_addon_default_post_meta_invalid_keys', array( '_edit_lock', '_edit_last', '_ithemes_sync_transaction_id' ) ) ) ) {
115
+ continue;
116
+ }
117
+ $value = maybe_unserialize( $value );
118
+ add_post_meta( $post->ID, $key, $value );
119
+
120
+ //Other add-ons might need to perform some extra actions with this new post meta (e.g. Membership)
121
+ do_action( 'ithemes_sync_duplicate_post_addon_add_post_meta', $post, $key, $value );
122
+ }
123
+ }
124
+ do_action( 'ithemes_sync_duplicate_post_addon_default_post_meta', $post, $duplicate_post_post_id );
125
+ }
126
+ }
127
+ add_action( 'add_meta_boxes', 'ithemes_sync_duplicate_post_addon_default_post_meta', 10, 2 );
functions.php ADDED
@@ -0,0 +1,1011 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Misc functions to assist the Sync code.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.9.0
7
+
8
+ Version History
9
+ 1.8.0 - 2014-03-28 - Chris Jean
10
+ Added find_match_in_file() and get_wordpress_db_version().
11
+ get_wordpress_version() now uses find_match_in_file().
12
+ 1.8.1 - 2014-04-14 - Chris Jean
13
+ The reported WordPress version now uses wp-includes/version.php in order to get a reliable version number.
14
+ Added mysqli_get_server_info entry to server status details in the event that WordPress is using mysqli.
15
+ 1.8.2 - 2014-05-19 - Chris Jean
16
+ Added checks to mysql_get_server_info() and mysqli_get_server_info() calls to avoid notices and errors.
17
+ 1.8.3 - 2014-06-23 - Chris Jean
18
+ Silenced possible notice-generating function calls by using @.
19
+ Replaced direct shell_exec() calls with calls to self::run_shell_command().
20
+ 1.8.4 - 2014-06-30 - Chris Jean
21
+ Use mysqli_get_host_info instead of mysql_get_host_info when mysqli is being used.
22
+ 1.8.5 - 2014-07-11 - Chris Jean
23
+ Added is_callable_function() which checks both is_callable() and the disable_functions ini setting to determine if a function is callable.
24
+ Replaced is_callable() calls for PHP functions with calls to self::is_callable_function() in order to avoid issues with servers that stop processing after a disabled function is run.
25
+ 1.8.6 - 2014-10-23 - Chris Jean
26
+ Added a fix to check for functions blacklisted by Suhosin before attempting to execute.
27
+ 1.8.7 - 2014-11-06 - Chris Jean
28
+ Fixed warnings that can happen when generating memory details on some systems.
29
+ 1.9.0 - 2016-07-20 - Lew Ayotte
30
+ Added get_upload_reports_dir
31
+ */
32
+
33
+
34
+ class Ithemes_Sync_Functions {
35
+ public static function get_url( $path ) {
36
+ $path = str_replace( '\\', '/', $path );
37
+ $wp_content_dir = str_replace( '\\', '/', WP_CONTENT_DIR );
38
+
39
+ if ( 0 === strpos( $path, $wp_content_dir ) ) {
40
+ return content_url( str_replace( $wp_content_dir, '', $path ) );
41
+ }
42
+
43
+ $abspath = str_replace( '\\', '/', ABSPATH );
44
+
45
+ if ( 0 === strpos( $path, $abspath ) ) {
46
+ return site_url( str_replace( $abspath, '', $path ) );
47
+ }
48
+
49
+ $wp_plugin_dir = str_replace( '\\', '/', WP_PLUGIN_DIR );
50
+ $wpmu_plugin_dir = str_replace( '\\', '/', WPMU_PLUGIN_DIR );
51
+
52
+ if ( 0 === strpos( $path, $wp_plugin_dir ) || 0 === strpos( $path, $wpmu_plugin_dir ) ) {
53
+ return plugins_url( basename( $path ), $path );
54
+ }
55
+
56
+ return false;
57
+ }
58
+
59
+ public static function get_post_data( $vars, $fill_missing = false, $merge_get_query = false ) {
60
+ $data = array();
61
+
62
+ foreach ( $vars as $var ) {
63
+ if ( isset( $_POST[$var] ) ) {
64
+ $clean_var = preg_replace( '/^it-updater-/', '', $var );
65
+ $data[$clean_var] = $_POST[$var];
66
+ }
67
+ else if ( $merge_get_query && isset( $_GET[$var] ) ) {
68
+ $clean_var = preg_replace( '/^it-updater-/', '', $var );
69
+ $data[$clean_var] = $_GET[$var];
70
+ }
71
+ else if ( $fill_missing ) {
72
+ $data[$var] = '';
73
+ }
74
+ }
75
+
76
+ return stripslashes_deep( $data );
77
+ }
78
+
79
+ public static function filter_user_has_cap( $capabilities, $caps, $args ) {
80
+ foreach ( $caps as $cap ) {
81
+ $capabilities[$cap] = 1;
82
+ }
83
+
84
+ return $capabilities;
85
+ }
86
+
87
+ public static function get_plugin_details( $args = array() ) {
88
+ if ( ! is_callable( 'get_plugins' ) ) {
89
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
90
+ }
91
+
92
+ if ( ! is_callable( 'get_plugins' ) ) {
93
+ return false;
94
+ }
95
+
96
+
97
+ $plugins = get_plugins();
98
+
99
+ $active_plugins = ( is_callable( 'wp_get_active_and_valid_plugins' ) ) ? wp_get_active_and_valid_plugins() : array();
100
+ $network_active_plugins = ( is_callable( 'wp_get_active_network_plugins' ) ) ? wp_get_active_network_plugins() : array();
101
+ // $mu_plugins = ( is_callable( 'get_mu_plugins' ) ) ? get_mu_plugins() : array();
102
+ // $dropins = ( is_callable( 'get_dropins' ) ) ? get_dropins() : array();
103
+
104
+ array_walk( $active_plugins, array( __CLASS__, 'strip_plugin_dir' ) );
105
+ array_walk( $network_active_plugins, array( __CLASS__, 'strip_plugin_dir' ) );
106
+
107
+ foreach ( $plugins as $plugin => $data ) {
108
+ if ( in_array( $plugin, $active_plugins ) ) {
109
+ $plugins[$plugin]['status'] = 'active';
110
+ } else if ( in_array( $plugin, $network_active_plugins ) ) {
111
+ $plugins[$plugin]['status'] = 'network_active';
112
+ } else {
113
+ $plugins[$plugin]['status'] = 'inactive';
114
+ }
115
+
116
+ if ( empty( $args['verbose'] ) ) {
117
+ unset( $plugins[$plugin]['PluginURI'] );
118
+ unset( $plugins[$plugin]['Description'] );
119
+ unset( $plugins[$plugin]['Author'] );
120
+ unset( $plugins[$plugin]['AuthorURI'] );
121
+ unset( $plugins[$plugin]['TextDomain'] );
122
+ unset( $plugins[$plugin]['DomainPath'] );
123
+ unset( $plugins[$plugin]['Title'] );
124
+ unset( $plugins[$plugin]['AuthorName'] );
125
+ } else {
126
+ $path = WP_PLUGIN_DIR . '/' . dirname( $plugin );
127
+
128
+ $vcs_details = self::get_repository_directory_details( $path );
129
+
130
+ if ( false !== $vcs_details ) {
131
+ $plugins[$plugin]['vcs'] = $vcs_details;
132
+ }
133
+ }
134
+ }
135
+
136
+
137
+ return $plugins;
138
+ }
139
+
140
+ public static function get_plugin_data( $path ) {
141
+ if ( ! is_callable( 'get_plugin_data' ) ) {
142
+ include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
143
+ }
144
+
145
+ if ( ! is_callable( 'get_plugin_data' ) ) {
146
+ return false;
147
+ }
148
+
149
+ $wp_plugin_dir = preg_replace( '|\\\|', '/', WP_PLUGIN_DIR );
150
+ $path = preg_replace( '|\\\|', '/', $path );
151
+
152
+ $path = preg_replace( '/^' . preg_quote( $wp_plugin_dir, '/' ) . '/', '', $path );
153
+ $path = preg_replace( '|^/+|', '', $path );
154
+ $path = WP_PLUGIN_DIR . "/$path";
155
+
156
+ $data = get_plugin_data( $path, false, false );
157
+
158
+ return $data;
159
+ }
160
+
161
+ public static function get_file_data( $file, $type = '' ) {
162
+ $headers = array(
163
+ 'name' => 'Name',
164
+ 'version' => 'Version',
165
+ 'description' => 'Description',
166
+ 'author' => 'Author',
167
+ 'author-uri' => 'Author URI',
168
+ 'text-domain' => 'Text Domain',
169
+ 'text-domain-path' => 'Domain Path',
170
+ 'ithemes-package' => 'iThemes Package',
171
+ );
172
+
173
+ $plugin_headers = array(
174
+ 'plugin-uri' => 'Plugin URI',
175
+ 'network' => 'Network',
176
+ 'sitewide' => '_sitewide',
177
+ );
178
+
179
+ $theme_headers = array(
180
+ 'theme-uri' => 'ThemeURI',
181
+ 'template' => 'Template',
182
+ 'status' => 'Status',
183
+ 'tags' => 'Tags',
184
+ );
185
+
186
+
187
+ if ( 'plugin' === $type ) {
188
+ $headers = array_merge( $headers, $plugin_headers );
189
+ } else if ( 'theme' === $type ) {
190
+ $headers = array_merge( $headers, $theme_headers );
191
+ }
192
+
193
+ return get_file_data( $file, $headers );
194
+ }
195
+
196
+ public static function find_match_in_file( $file, $expression, $index = false ) {
197
+ $fh = fopen( $file, 'r' );
198
+
199
+ $data = '';
200
+ $retval = false;
201
+
202
+ while ( $file_read = fread( $fh, 256 ) ) {
203
+ $data .= $file_read;
204
+
205
+ if ( preg_match( $expression, $data, $match ) ) {
206
+ $retval = $match;
207
+ break;
208
+ }
209
+ }
210
+
211
+ fclose( $fh );
212
+
213
+ if ( false !== $index ) {
214
+ if ( is_array( $retval ) && isset( $retval[$index] ) ) {
215
+ return $retval[$index];
216
+ } else {
217
+ return false;
218
+ }
219
+ }
220
+
221
+ return $retval;
222
+ }
223
+
224
+ public static function get_wordpress_version() {
225
+ return self::find_match_in_file( ABSPATH . WPINC . '/version.php', "/\\\$wp_version\s*=\s*(['\"])([^'\"]+)\\1/", 2 );
226
+ }
227
+
228
+ public static function get_wordpress_db_version() {
229
+ return self::find_match_in_file( ABSPATH . WPINC . '/version.php', "/\\\$wp_db_version\s*=\s*['\"]?(\d+)/", 1 );
230
+ }
231
+
232
+ public static function strip_plugin_dir( &$path ) {
233
+ $path = preg_replace( '|^' . preg_quote( WP_PLUGIN_DIR, '|' ) . '/|', '', $path );
234
+ }
235
+
236
+ public static function get_theme_details( $args = array() ) {
237
+ if ( ! is_callable( 'wp_get_themes' ) ) {
238
+ return false;
239
+ }
240
+
241
+
242
+ $themes = array();
243
+
244
+ $active_stylesheet = basename( get_stylesheet_directory() );
245
+ $active_template = basename( get_template_directory() );
246
+
247
+ foreach ( wp_get_themes() as $dir => $theme ) {
248
+ $data = array(
249
+ 'name' => $theme['Name'],
250
+ 'version' => $theme['Version'],
251
+ 'parent' => $theme->parent_theme,
252
+ );
253
+
254
+ if ( ! empty( $args['verbose'] ) ) {
255
+ $data['description'] = $theme['Description'];
256
+ $data['author'] = $theme['Author Name'];
257
+ $data['author-uri'] = $theme['Author URI'];
258
+
259
+
260
+ $vcs_details = self::get_repository_directory_details( $theme['Stylesheet Dir'] );
261
+
262
+ if ( false !== $vcs_details ) {
263
+ $data['vcs'] = $vcs_details;
264
+ }
265
+ }
266
+
267
+ if ( empty( $data['parent'] ) ) {
268
+ unset( $data['parent'] );
269
+ } else {
270
+ $data['parent'] = $theme->parent()->stylesheet;
271
+ }
272
+
273
+ if ( $dir == $active_stylesheet ) {
274
+ $data['status'] = 'active';
275
+ } else if ( $dir == $active_template ) {
276
+ $data['status'] = 'active_parent';
277
+ } else {
278
+ $data['status'] = '';
279
+ }
280
+
281
+
282
+ $themes[$dir] = $data;
283
+ }
284
+
285
+
286
+ return $themes;
287
+ }
288
+
289
+ public static function refresh_plugin_updates() {
290
+ require_once( ABSPATH . 'wp-includes/update.php' );
291
+
292
+ if ( is_callable( 'wp_update_plugins' ) ) {
293
+ return wp_update_plugins();
294
+ }
295
+
296
+ return false;
297
+ }
298
+
299
+ public static function refresh_theme_updates() {
300
+ require_once( ABSPATH . 'wp-includes/update.php' );
301
+
302
+ if ( is_callable( 'wp_update_themes' ) ) {
303
+ return wp_update_themes();
304
+ }
305
+
306
+ return false;
307
+ }
308
+
309
+ public static function refresh_core_updates() {
310
+ require_once( ABSPATH . 'wp-includes/update.php' );
311
+
312
+ if ( is_callable( 'wp_version_check' ) ) {
313
+ return wp_version_check( array(), true );
314
+ }
315
+
316
+ return false;
317
+ }
318
+
319
+ public static function get_update_details( $args = array() ) {
320
+ if ( ! empty( $args['ithemes-updater-force-refresh'] ) && isset( $GLOBALS['ithemes-updater-settings'] ) ) {
321
+ $GLOBALS['ithemes-updater-settings']->flush( 'forced sync flush' );
322
+ }
323
+
324
+ $default_args = array(
325
+ 'verbose' => false,
326
+ 'force_refresh' => false,
327
+ );
328
+ $args = array_merge( $default_args, $args );
329
+
330
+
331
+ $updates = array(
332
+ 'plugins' => array(),
333
+ 'themes' => array(),
334
+ 'translations' => array(),
335
+ 'core' => array(),
336
+ );
337
+
338
+
339
+ if ( is_array( $args['force_refresh'] ) ) {
340
+ if ( in_array( 'plugins', $args['force_refresh'] ) ) {
341
+ $updates['force-refresh-results']['plugins'] = self::refresh_plugin_updates();
342
+ }
343
+ if ( in_array( 'themes', $args['force_refresh'] ) ) {
344
+ $updates['force-refresh-results']['themes'] = self::refresh_theme_updates();
345
+ }
346
+ if ( in_array( 'core', $args['force_refresh'] ) ) {
347
+ $updates['force-refresh-results']['core'] = self::refresh_core_updates();
348
+ }
349
+ } else if ( $args['force_refresh'] ) {
350
+ $updates['force-refresh-results']['plugins'] = self::refresh_plugin_updates();
351
+ $updates['force-refresh-results']['themes'] = self::refresh_theme_updates();
352
+ $updates['force-refresh-results']['core'] = self::refresh_core_updates();
353
+ }
354
+
355
+
356
+ $update_plugins = get_site_transient( 'update_plugins' );
357
+
358
+ if ( ! empty( $update_plugins->response ) ) {
359
+ $updates['plugins'] = $update_plugins->response;
360
+
361
+ if ( empty( $args['verbose'] ) ) {
362
+ foreach ( $updates['plugins'] as $plugin => $data ) {
363
+ unset( $updates['plugins'][$plugin]->id );
364
+ unset( $updates['plugins'][$plugin]->slug );
365
+ unset( $updates['plugins'][$plugin]->url );
366
+ unset( $updates['plugins'][$plugin]->package );
367
+ }
368
+ }
369
+ }
370
+
371
+ if ( ! empty( $update_plugins->translations ) ) {
372
+ $updates['translations'] = array_merge( $updates['translations'], $update_plugins->translations );
373
+ }
374
+
375
+
376
+ $update_themes = get_site_transient( 'update_themes' );
377
+
378
+ if ( ! empty( $update_themes->response ) ) {
379
+ $updates['themes'] = $update_themes->response;
380
+
381
+ if ( empty( $args['verbose'] ) ) {
382
+ foreach ( $updates['themes'] as $theme => $data ) {
383
+ unset( $updates['themes'][$theme]['package'] );
384
+ unset( $updates['themes'][$theme]['url'] );
385
+ }
386
+ }
387
+ }
388
+
389
+ if ( ! empty( $update_themes->translations ) ) {
390
+ $updates['translations'] = array_merge( $updates['translations'], $update_themes->translations );
391
+ }
392
+
393
+
394
+ $update_core = get_site_transient( 'update_core' );
395
+
396
+ if ( ! empty( $update_core->updates ) ) {
397
+ $updates['core'] = $update_core->updates;
398
+
399
+ foreach ( $updates['core'] as $index => $update ) {
400
+ if ( empty( $update->current ) && ! empty( $update->version ) ) {
401
+ $updates['core'][$index]->current = $update->version;
402
+ } else if ( empty( $update->version ) && ! empty( $update->current ) ) {
403
+ $updates['core'][$index]->version = $update->current;
404
+ }
405
+
406
+ if ( empty( $args['verbose'] ) ) {
407
+ unset( $updates['core'][$index]->download );
408
+ unset( $updates['core'][$index]->packages );
409
+ unset( $updates['core'][$index]->php_version );
410
+ unset( $updates['core'][$index]->mysql_version );
411
+ unset( $updates['core'][$index]->new_bundled );
412
+ unset( $updates['core'][$index]->partial_version );
413
+ }
414
+ }
415
+ }
416
+
417
+ if ( ! empty( $update_core->translations ) ) {
418
+ $updates['translations'] = array_merge( $updates['translations'], $update_core->translations );
419
+ }
420
+
421
+
422
+ return $updates;
423
+ }
424
+
425
+ public static function get_wordpress_details( $args = array() ) {
426
+ $details = array(
427
+ 'version' => self::get_wordpress_version(),
428
+ 'url' => get_bloginfo( 'url' ),
429
+ 'wpurl' => get_bloginfo( 'wpurl' ),
430
+ 'login-url' => wp_login_url(),
431
+ 'admin-url' => admin_url(),
432
+ );
433
+
434
+ if ( is_callable( 'is_multisite' ) ) {
435
+ if ( is_multisite() ) {
436
+ $details['multisite'] = true;
437
+
438
+ if ( is_callable( 'get_current_blog_id' ) ) {
439
+ $details['blogid'] = get_current_blog_id();
440
+ } else if ( isset( $GLOBALS['blogid'] ) ) {
441
+ $details['blogid'] = $GLOBALS['blogid'];
442
+ }
443
+ }
444
+ else {
445
+ $details['multisite'] = false;
446
+ }
447
+ }
448
+
449
+ if ( ! empty( $args['verbose'] ) ) {
450
+ $vcs_details = self::get_repository_directory_details( ABSPATH );
451
+
452
+ if ( false !== $vcs_details ) {
453
+ $details['vcs'] = $vcs_details;
454
+ }
455
+ }
456
+
457
+ return $details;
458
+ }
459
+
460
+ public static function get_php_details( $args = array() ) {
461
+ $details['display_errors'] = $GLOBALS['ithemes_sync_request_handler']->original_display_errors;
462
+ $details['error_reporting'] = $GLOBALS['ithemes_sync_request_handler']->original_error_reporting;
463
+
464
+ if ( self::is_callable_function( 'ini_get' ) ) {
465
+ $details['disable_functions'] = ini_get( 'disable_functions' );
466
+ $details['suhosin.executor.func.blacklist'] = ini_get( 'suhosin.executor.func.blacklist' );
467
+ }
468
+
469
+
470
+ $functions = array(
471
+ 'phpversion',
472
+ 'PHP_VERSION',
473
+ 'php_sapi_name',
474
+ 'PHP_SAPI',
475
+ );
476
+
477
+ $details = self::get_function_results( $functions, $details );
478
+
479
+
480
+ if ( empty( $args['verbose'] ) ) {
481
+ return $details;
482
+ }
483
+
484
+
485
+ $functions = array(
486
+ 'zend_version',
487
+ 'sys_get_temp_dir',
488
+ 'get_loaded_extensions',
489
+ );
490
+
491
+ $details = self::get_function_results( $functions, $details );
492
+
493
+
494
+ if ( self::is_callable_function( 'phpinfo' ) ) {
495
+ ob_start();
496
+ phpinfo();
497
+
498
+ $phpinfo = ob_get_clean();
499
+ $phpinfo = preg_replace( '/<[^>]*>/', ' ', $phpinfo );
500
+ $phpinfo = html_entity_decode( $phpinfo, ENT_QUOTES );
501
+
502
+ $patterns = array(
503
+ 'php-version' => '/^\s*PHP Version\s+(.+)\s*$/mi',
504
+ 'build-system' => '/^\s*System\s+(.+)\s*$/mi',
505
+ 'configure' => '/^\s*Configure Command\s+(.+)\s*$/mi',
506
+ 'server-api' => '/^\s*Server API\s+(.+)\s*$/mi',
507
+ 'gd-support' => '/^\s*GD Support\s+(.+)\s*$/mi',
508
+ 'json-support' => '/^\s*json support\s+(.+)\s*$/mi',
509
+ 'mb-support' => '/^\s*Multibyte Support\s+(.+)\s*$/mi',
510
+ 'server-software' => '/^\s*SERVER_SOFTWARE\s+(.+)\s*$/mi',
511
+ );
512
+
513
+ $details['phpinfo'] = self::get_pattern_results( $phpinfo, $patterns );
514
+ }
515
+
516
+
517
+ return $details;
518
+ }
519
+
520
+ public static function get_server_details( $args = array() ) {
521
+ $details = array(
522
+ 'timezone_string' => get_option( 'timezone_string' ),
523
+ 'gmt_offset' => get_option( 'gmt_offset' ),
524
+ 'ini.date.timezone' => ini_get( 'date.timezone' ),
525
+ );
526
+
527
+ $timezone = self::run_shell_command( 'date +%Z 2>/dev/null' );
528
+
529
+ if ( ! empty( $timezone ) ) {
530
+ $details['date +%Z'] = $timezone;
531
+ $details['date +%z'] = self::run_shell_command( 'date +%z 2>/dev/null' );
532
+ $details['date +%s'] = self::run_shell_command( 'date +%s 2>/dev/null' );
533
+ }
534
+
535
+
536
+ $functions = array(
537
+ 'time',
538
+ 'date_default_timezone_get',
539
+ 'sys_getloadavg',
540
+ 'php_uname',
541
+ 'PHP_OS',
542
+ 'memory_get_usage',
543
+ 'memory_get_peak_usage',
544
+ );
545
+
546
+ $details = self::get_function_results( $functions, $details );
547
+
548
+ if ( ! isset( $GLOBALS['wpdb'] ) || ( empty( $GLOBALS['wpdb']->use_mysqli ) && self::is_callable_function( 'mysql_get_server_info' ) ) ) {
549
+ $details['mysql_get_server_info'] = @mysql_get_server_info();
550
+ } else if ( isset( $GLOBALS['wpdb']->dbh ) && self::is_callable_function( 'mysqli_get_server_info' ) ) {
551
+ $details['mysqli_get_server_info'] = @mysqli_get_server_info( $GLOBALS['wpdb']->dbh );
552
+ }
553
+
554
+ $details['SERVER_ADDR'] = @$_SERVER['SERVER_ADDR'];
555
+
556
+
557
+ if ( empty( $args['verbose'] ) ) {
558
+ return $details;
559
+ }
560
+
561
+
562
+ $functions = array(
563
+ 'PHP_EOL',
564
+ 'DIRECTORY_SEPARATOR',
565
+ );
566
+
567
+ $details = self::get_function_results( $functions, $details );
568
+
569
+ if ( ! isset( $GLOBALS['wpdb'] ) || ( empty( $GLOBALS['wpdb']->use_mysqli ) && self::is_callable_function( 'mysql_get_host_info' ) ) ) {
570
+ $details['mysql_get_host_info'] = @mysql_get_host_info();
571
+ } else if ( isset( $GLOBALS['wpdb']->dbh ) && self::is_callable_function( 'mysqli_get_host_info' ) ) {
572
+ $details['mysqli_get_host_info'] = @mysqli_get_host_info( $GLOBALS['wpdb']->dbh );
573
+ }
574
+
575
+ $details['server_ip'] = self::get_server_ip();
576
+
577
+
578
+ $commands = array(
579
+ 'lsb_release -a',
580
+ 'cat /etc/*-release',
581
+ 'who',
582
+ 'df -h',
583
+ 'ps aux|wc -l',
584
+ 'ps aux --sort=-%cpu|head -6',
585
+ 'ps aux --sort=-%mem|head -6',
586
+ );
587
+
588
+ $details = self::get_shell_command_results( $commands, $details );
589
+
590
+
591
+ $cpuinfo = self::run_shell_command( 'cat /proc/cpuinfo' );
592
+
593
+ if ( preg_match_all( '/model name\s*:\s*([^\r\n]+).*?cpu MHz\s*:\s*([^\r\n]+).*?physical id\s*:\s*([^\r\n]+).*?siblings\s*:\s*([^\r\n]+).*?cpu cores\s*:\s*([^\r\n]+)/s', $cpuinfo, $matches, PREG_SET_ORDER ) ) {
594
+ foreach ( $matches as $match ) {
595
+ $details['cpus'][$match[3]] = array(
596
+ 'model' => $match[1],
597
+ 'mhz' => $match[2],
598
+ 'siblings' => $match[4],
599
+ 'cores' => $match[5],
600
+ );
601
+ }
602
+ }
603
+
604
+
605
+ $memory_data = self::run_shell_command( '/usr/bin/free|grep -i "^Mem:"' );
606
+
607
+ if ( ! empty( $memory_data ) ) {
608
+ $memory_data = preg_split( '/\s+/', $memory_data );
609
+
610
+ $memory = array(
611
+ 'total' => $memory_data[1],
612
+ 'used' => $memory_data[2],
613
+ 'free' => $memory_data[3],
614
+ 'buffers' => $memory_data[5],
615
+ 'cache' => $memory_data[6],
616
+ );
617
+
618
+ $memory['used-real'] = $memory['used'] - $memory['buffers'] - $memory['cache'];
619
+ $memory['free-real'] = $memory['total'] - $memory['used-real'];
620
+
621
+
622
+ $swap_data = self::run_shell_command( '/usr/bin/free|grep -i "^Swap:"' );
623
+
624
+ if ( ! empty( $swap_data ) ) {
625
+ $swap_data = preg_split( '/\s+/', $swap_data );
626
+
627
+ $memory['swap'] = array(
628
+ 'total' => $swap_data[1],
629
+ 'used' => $swap_data[2],
630
+ 'free' => $swap_data[3],
631
+ );
632
+ }
633
+
634
+
635
+ $details['memory'] = $memory;
636
+ } else if ( file_exists( '/proc/meminfo' ) && ( false !== ( $meminfo = file_get_contents( '/proc/meminfo' ) ) ) && preg_match_all( '/^([^:]+):\s+(\d+)/m', $meminfo, $matches, PREG_SET_ORDER ) ) {
637
+ $memory_data = array();
638
+
639
+ foreach ( $matches as $match ) {
640
+ $memory_data[$match[1]] = $match[2];
641
+ }
642
+
643
+ $memory = array(
644
+ 'total' => $memory_data['MemTotal'],
645
+ 'used' => (string) ( $memory_data['MemTotal'] - $memory_data['MemFree'] ),
646
+ 'free' => $memory_data['MemFree'],
647
+ 'buffers' => $memory_data['Buffers'],
648
+ 'cache' => $memory_data['Cached'],
649
+ );
650
+
651
+ $memory['used-real'] = $memory['used'] - $memory['buffers'] - $memory['cache'];
652
+ $memory['free-real'] = $memory['total'] - $memory['used-real'];
653
+
654
+
655
+ $memory['swap'] = array(
656
+ 'total' => $memory_data['SwapTotal'],
657
+ 'used' => (string) ( $memory_data['SwapTotal'] - $memory_data['SwapFree'] ),
658
+ 'free' => $memory_data['SwapFree'],
659
+ );
660
+
661
+
662
+ $details['memory'] = $memory;
663
+ }
664
+
665
+
666
+ return $details;
667
+ }
668
+
669
+ private static function get_server_ip() {
670
+ $data = wp_remote_get( 'http://ithemes.com/utilities/show-remote-ip.php' );
671
+
672
+ if ( ! is_wp_error( $data ) && preg_match( '/(\d+\.\d+\.\d+\.\d+)/', $data['body'], $match ) ) {
673
+ return $match[1];
674
+ }
675
+
676
+
677
+ $data = wp_remote_get( 'http://ip4.me/' );
678
+
679
+ if ( ! is_wp_error( $data ) && preg_match( '/>(\d+\.\d+\.\d+\.\d+)</', $data['body'], $match ) ) {
680
+ return $match[1];
681
+ }
682
+
683
+
684
+ return false;
685
+ }
686
+
687
+ private static function get_function_results( $functions, $data = array() ) {
688
+ foreach ( $functions as $function ) {
689
+ $var = $function;
690
+
691
+ if ( false === strpos( $function, '|' ) ) {
692
+ $args = array();
693
+ } else {
694
+ $args = explode( '|', $function );
695
+ $function = array_shift( $args );
696
+
697
+ if ( ( 1 === count( $args ) ) && ( 0 === strpos( $args[0], '[' ) ) ) {
698
+ $new_args = @json_decode( $args[0] );
699
+
700
+ if ( ! is_null( $new_args ) ) {
701
+ $args = $new_args;
702
+ }
703
+ }
704
+ }
705
+
706
+ if ( self::is_callable_function( $function ) ) {
707
+ $data[$var] = call_user_func_array( $function, $args );
708
+ } else if ( defined( $function ) && empty( $args ) ) {
709
+ $data[$var] = constant( $function );
710
+ }
711
+ }
712
+
713
+ return $data;
714
+ }
715
+
716
+ private static function get_pattern_results( $raw_data, $patterns, $data = array() ) {
717
+ foreach ( $patterns as $name => $pattern ) {
718
+ if ( preg_match( $pattern, $raw_data, $match ) ) {
719
+ $data[$name] = $match[1];
720
+ }
721
+ }
722
+
723
+ return $data;
724
+ }
725
+
726
+ private static function get_shell_command_results( $commands, $data = array() ) {
727
+ foreach ( $commands as $command ) {
728
+ $result = self::run_shell_command( $command );
729
+
730
+ if ( false !== $result ) {
731
+ $data[$command] = $result;
732
+ }
733
+ }
734
+
735
+ return $data;
736
+ }
737
+
738
+ private static function run_shell_command( $command ) {
739
+ $command = 'PATH="$PATH:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/sbin"; ' . $command;
740
+
741
+ if ( self::is_callable_function( 'shell_exec' ) ) {
742
+ $result = @shell_exec( $command );
743
+
744
+ if ( is_null( $result ) ) {
745
+ return false;
746
+ }
747
+
748
+ return $result;
749
+ }
750
+
751
+ if ( self::is_callable_function( 'exec' ) ) {
752
+ @exec( $command, $results, $status );
753
+
754
+ if ( ! empty( $results ) ) {
755
+ return implode( "\n", $results );
756
+ } else if( empty( $status ) ) {
757
+ return '';
758
+ } else {
759
+ return false;
760
+ }
761
+ }
762
+
763
+ if ( self::is_callable_function( 'system' ) ) {
764
+ ob_start();
765
+ $return = @system( $command, $status );
766
+ $result = ob_get_clean();
767
+
768
+ if ( false === $return ) {
769
+ return false;
770
+ } else if ( ! empty( $result ) ) {
771
+ return $result;
772
+ } else if ( empty( $status ) ) {
773
+ return '';
774
+ } else {
775
+ return false;
776
+ }
777
+ }
778
+
779
+ if ( self::is_callable_function( 'passthru' ) ) {
780
+ ob_start();
781
+ $return = @passthru( $command, $status );
782
+ $result = ob_get_clean();
783
+
784
+ if ( false === $return ) {
785
+ return false;
786
+ } else if ( ! empty( $result ) ) {
787
+ return $result;
788
+ } else if ( empty( $status ) ) {
789
+ return '';
790
+ } else {
791
+ return false;
792
+ }
793
+ }
794
+
795
+ return false;
796
+ }
797
+
798
+ public static function merge_defaults( $values, $defaults, $force = false ) {
799
+ if ( ! self::is_associative_array( $defaults ) ) {
800
+ if ( ! isset( $values ) ) {
801
+ return $defaults;
802
+ }
803
+
804
+ if ( false === $force ) {
805
+ return $values;
806
+ }
807
+
808
+ if ( isset( $values ) || is_array( $values ) ) {
809
+ return $values;
810
+ }
811
+
812
+ return $defaults;
813
+ }
814
+
815
+ foreach ( (array) $defaults as $key => $val ) {
816
+ if ( ! isset( $values[$key] ) ) {
817
+ $values[$key] = null;
818
+ }
819
+
820
+ $values[$key] = self::merge_defaults( $values[$key], $val, $force );
821
+ }
822
+
823
+ return $values;
824
+ }
825
+
826
+ public static function is_associative_array( &$array ) {
827
+ if ( ! is_array( $array ) || empty( $array ) ) {
828
+ return false;
829
+ }
830
+
831
+ $next = 0;
832
+
833
+ foreach ( $array as $k => $v ) {
834
+ if ( $k !== $next++ ) {
835
+ return true;
836
+ }
837
+ }
838
+
839
+ return false;
840
+ }
841
+
842
+ public static function get_users( $query_args = array() ) {
843
+ $default_query_args = array(
844
+ 'blog_id' => 0,
845
+ );
846
+ $query_args = array_merge( $default_query_args, $query_args );
847
+
848
+ if ( ! empty( $query_args['capability'] ) ) {
849
+ $capabilities = (array) $query_args['capability'];
850
+ unset( $query_args['capability'] );
851
+ }
852
+
853
+ $all_users = get_users( $query_args );
854
+
855
+ $users = array();
856
+
857
+ foreach ( $all_users as $user ) {
858
+ if ( ! empty( $capabilities ) ) {
859
+ $user_can = true;
860
+
861
+ foreach ( (array) $capabilities as $capability ) {
862
+ if ( ! user_can( $user, $capability ) ) {
863
+ $user_can = false;
864
+ break;
865
+ }
866
+ }
867
+
868
+ if ( ! $user_can ) {
869
+ continue;
870
+ }
871
+ }
872
+
873
+ $users[$user->ID] = array(
874
+ 'login' => $user->data->user_login,
875
+ 'display_name' => $user->data->display_name,
876
+ );
877
+ }
878
+
879
+
880
+ return $users;
881
+ }
882
+
883
+ public static function get_sync_settings( $args = array() ) {
884
+ $all_settings = $GLOBALS['ithemes-sync-settings']->get_options();
885
+
886
+ if ( ! empty( $args['settings'] ) ) {
887
+ $keys = $args['settings'];
888
+ } else if ( ! empty( $args['verbose'] ) ) {
889
+ $keys = array_keys( $all_settings );
890
+
891
+ $keys = array_flip( $keys );
892
+ unset( $keys['authentications'] );
893
+ $keys = array_flip( $keys );
894
+ } else {
895
+ $keys = array(
896
+ 'show_sync',
897
+ );
898
+ }
899
+
900
+ $settings = array();
901
+
902
+ foreach ( $keys as $key ) {
903
+ if ( isset( $all_settings[$key] ) ) {
904
+ $settings[$key] = $all_settings[$key];
905
+ } else {
906
+ $settings[$key] = null;
907
+ }
908
+ }
909
+
910
+ if ( ! in_array( 'authentications', $keys ) && isset( $settings['authentications'] ) ) {
911
+ unset( $settings['authentications'] );
912
+ }
913
+
914
+
915
+ return $settings;
916
+ }
917
+
918
+ public static function get_supported_verbs( $args = array() ) {
919
+ if ( ! is_callable( array( $GLOBALS['ithemes-sync-api'], 'get_descriptions' ) ) ) {
920
+ return new WP_Error( 'missing-method-api-get_descriptions', 'The Ithemes_Sync_API::get_descriptions function is not callable.' );
921
+ }
922
+
923
+ return $GLOBALS['ithemes-sync-api']->get_names();
924
+ }
925
+
926
+ public static function get_status_elements( $args = array() ) {
927
+ if ( ! is_callable( array( $GLOBALS['ithemes-sync-api'], 'get_status_elements' ) ) ) {
928
+ return new WP_Error( 'missing-method-api-get_status_elements', 'The Ithemes_Sync_API::get_status_elements function is not callable.' );
929
+ }
930
+
931
+ return $GLOBALS['ithemes-sync-api']->get_status_elements();
932
+ }
933
+
934
+ public static function get_default_status_elements( $args = array() ) {
935
+ if ( ! is_callable( array( $GLOBALS['ithemes-sync-api'], 'get_default_status_elements' ) ) ) {
936
+ return new WP_Error( 'missing-method-api-get_default_status_elements', 'The Ithemes_Sync_API::get_default_status_elements function is not callable.' );
937
+ }
938
+
939
+ return $GLOBALS['ithemes-sync-api']->get_default_status_elements();
940
+ }
941
+
942
+ public static function set_time_limit( $seconds = 60 ) {
943
+ if ( is_callable( 'set_time_limit' ) ) {
944
+ @set_time_limit( $seconds );
945
+ }
946
+ }
947
+
948
+ public static function get_repository_directory_details( $path ) {
949
+ $vcs_types = array(
950
+ '.git' => array(
951
+ 'name' => 'git',
952
+ ),
953
+ '.svn' => array(
954
+ 'name' => 'subversion',
955
+ ),
956
+ '.hg' => array(
957
+ 'name' => 'mercurial',
958
+ ),
959
+ '.bzr' => array(
960
+ 'name' => 'bazaar',
961
+ ),
962
+ );
963
+
964
+ foreach ( $vcs_types as $directory => $details ) {
965
+ if ( is_dir( "$path/$directory" ) ) {
966
+ return $details;
967
+ }
968
+ }
969
+
970
+ return false;
971
+ }
972
+
973
+ public static function is_callable_function( $function ) {
974
+ if ( ! is_callable( $function ) ) {
975
+ return false;
976
+ }
977
+
978
+ $disabled_functions = preg_split( '/\s*,\s*/', (string) ini_get( 'disable_functions' ) );
979
+
980
+ if ( in_array( $function, $disabled_functions ) ) {
981
+ return false;
982
+ }
983
+
984
+ $disabled_functions = preg_split( '/\s*,\s*/', (string) ini_get( 'suhosin.executor.func.blacklist' ) );
985
+
986
+ if ( in_array( $function, $disabled_functions ) ) {
987
+ return false;
988
+ }
989
+
990
+ return true;
991
+ }
992
+
993
+ public static function get_upload_reports_dir() {
994
+ $wp_upload_dir = wp_upload_dir();
995
+ $reports_path = apply_filters( 'get_upload_reports_dir', $wp_upload_dir['basedir'] . '/reports' );
996
+
997
+ if ( ! file_exists( $reports_path ) ) {
998
+ wp_mkdir_p( $reports_path );
999
+ }
1000
+
1001
+ return $reports_path;
1002
+ }
1003
+
1004
+ public static function get_upload_reports_url() {
1005
+ $wp_upload_dir = wp_upload_dir();
1006
+ $reports_url = apply_filters( 'get_upload_reports_url', $wp_upload_dir['baseurl'] . '/reports' );
1007
+
1008
+ return $reports_url;
1009
+ }
1010
+
1011
+ }
history.txt ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1.0.0 - 2013-11-11 - Chris Jean
2
+ iThemes Sync launch!
3
+ 1.1.0 - 2013-11-20 - Chris Jean
4
+ Enhancement: Added ability to handle desync user requests from the Sync server.
5
+ Enhancement: Now avoids plugin conflicts that caused plugins and themes to not be properly reported and which prevented updates from being successfully applied.
6
+ Enhancement: Now supports update reporting for and updating of plugins with updater code that only works with pre-3.0 versions of WordPress.
7
+ Enhancement: Now supports sending local server status information back to the Sync server.
8
+ 1.1.1 - 2013-11-20 - Chris Jean
9
+ Bug Fix: Requests for the plugin settings would return the authentications data. Now, the authentications data must be explicitly requested in order to be retrieved.
10
+ 1.1.2 - 2013-11-20 - Chris Jean
11
+ Enhancement: By default, pulling update details will not force a flush of all the cached update data. Instead, this can now be specifically requested by the server.
12
+ 1.1.3 - 2013-11-22 - Chris Jean
13
+ Enhancement: The server will now receive a listing of the supported verbs with a status check. This will help the Sync Dashboard determine if the plugin needs to be upgraded in order to access specific features.
14
+ 1.1.4 - 2013-11-26 - Chris Jean
15
+ Bug Fix: Removed call to the mysqli_get_server_info() function when pulling server details as it would trigger errors on some systems.
16
+ Bug Fix: Removed the /etc/passwd check when pulling server details. This was used to try to count the number of users on the system. It was removed since it triggered hacking attempt notices in the Wordfence plugin.
17
+ 1.1.5 - 2013-12-02 - Chris Jean
18
+ Bug Fix: Users that are not recognized by the Sync server can now be unsynced. This prevents the issue where users that are unsynced on the server when the site cannot be communicated with (or when the plugin is inactive) cannot be removed from the site.
19
+ 1.1.6 - 2013-12-09 - Chris Jean
20
+ Bug Fix: Fixed issue that can cause failure to load plugin details on sites that have a different url and wpurl.
21
+ 1.1.7 - 2013-12-12 - Chris Jean
22
+ Enhancement: Updated the styling to work better with WordPress 3.8.
23
+ 1.1.8 - 2013-12-13 - Packaging Bot (lib/updater)
24
+ Enhancement: Recently updated plugins and themes no longer report that an update is available.
25
+ Enhancement: Added alternate line styling to the listings on the licensing page.
26
+ Enhancement: Products with iThemes in the name will now properly capitalize the name.
27
+ Enhancement: The updater and licensing system now handle iThemes server outages without causing long page delays or by reporting confusing error messages.
28
+ 1.1.9 - 2013-12-13 - Chris Jean
29
+ Enhancement: The Sync server can now create requests that force the iThemes updater system to refresh itself.
30
+ 1.1.10 - 2013-12-16 - Chris Jean
31
+ Enhancement: Added user security privileges when handling requests from the Sync server. This helps avoid issues with some security plugins.
32
+ 1.1.11 - 2013-12-18 - Packaging Bot (lib/updater)
33
+ Bug Fix: Removed unneeded check on package-info.ithemes.com which was causing performance issues on some sites.
34
+ 1.1.12 - 2013-12-19 - Chris Jean
35
+ Enhancement: Improved the get-status request so that the Sync server can request only the specific data it needs, causing less load and faster Sync responses.
36
+ 1.1.13 - 2013-12-19 - Packaging Bot (lib/updater)
37
+ Bug Fix: Licensing will now be universal across all sites of a multisite network. This means that you will only need to license on one site to have the license apply across the entire network.
38
+ 1.2.0 - 2014-01-20 - Chris Jean
39
+ Enhancement: Added the ability to manage plugins and themes.
40
+ Enhancement: get-status requests now support variable display elements.
41
+ 1.3.0 - 2014-01-29 - Chris Jean
42
+ Enhancement: Added the ability to install plugins and themes.
43
+ Bug Fix: Active plugins will be deactivated before uninstalling.
44
+ 1.3.1 - 2014-02-03 - Packaging Bot (lib/updater)
45
+ Bug Fix: Licensing on SSL-enabled sites now works properly.
46
+ 1.3.2 - 2014-02-18 - Chris Jean
47
+ Enhancement: Adding ?ithemes-sync-force-display=1 to an admin page URL while logged in as a user that has manage-options privileges will force a site that has Sync hidden to display Sync temporarily for that user.
48
+ Enhancement: Data sent back for update requests now includes more data to help identify systems that don't accurately report if the update was successful or not.
49
+ Enhancement: Improved error messages for issues communicating with the Sync server.
50
+ Enhancement: Added a compatibility check to ensure that Gravity Forms's updates are showing and able to be applied.
51
+ Enhancement: Added checks to provide informational messages if more than one iThemes Sync plugin is active on a site.
52
+ Enhancement: Improved styling of admin notices on the Settings > iThemes Sync page.
53
+ Bug Fix: Increased the amount of time that the plugin will wait for responses from the Sync server when authenticating and deauthenting. This fixes the issue where some servers could not sync or unsync users due to server communication time outs.
54
+ 1.3.3 - 2014-02-19 - Chris Jean
55
+ Bug Fix: Changed code that caused compatibility issues with some servers.
56
+ 1.3.4 - 2014-02-21 - Chris Jean
57
+ Bug Fix: Compatibility fix for plugins that corrupt the data sent back to the Sync server.
58
+ 1.3.5 - 2014-03-07 - Chris Jean
59
+ Enhancement: Valid requests from the Sync server will cause the plugin pretend to be an Administrator user. This is to avoid compatibility issues with some security plugins.
60
+ 1.4.0 - 2014-03-28 - Chris Jean
61
+ Bug Fix: Updating a WordPress multisite will now properly apply the network upgrade after updating.
62
+ Enhancement: Users that are not recognized by the server are now identified on the Settings > iThemes Sync page.
63
+ New Feature: Plugins and themes can now send notices to Sync. Details in api.txt.
64
+ 1.4.1 - 2014-04-04 - Chris Jean
65
+ Enhancement: The notice feature now supports more options, making it ready for use.
66
+ 1.4.2 - 2014-04-15 - Chris Jean
67
+ Enhancement: Made small adjustments to prepare for WordPress 3.9.
68
+ Bug Fix: The WordPress version is now correctly sent back to the Sync server, even when a plugin or other modification modifies the version number. This fixes issues with WordPress updates consistently being available, even when it is up-to-date.
69
+ 1.5.0 - 2014-05-20 - Chris Jean
70
+ Enhancement: Improved feedback when installing themes and plugins.
71
+ Enhancement: Added the ability to install and activate a plugin or theme in one request.
72
+ Bug Fix: Notices generated by mysql_get_server_info() and mysqli_get_server_info() are now hidden and will not fill error logs.
73
+ 1.5.1 - 2014-06-26 - Chris Jean
74
+ Bug Fix: Users can now properly be synced on sites that report the WordPress version incorrectly.
75
+ 1.6.0 - 2014-07-01 - Chris Jean
76
+ Enhancement: Added ability to manage comments, roles, and users.
77
+ 1.6.1 - 2014-07-11 - Chris Jean
78
+ Bug Fix: Servers that have strict disable_functions settings that also stop PHP execution when a disabled function is called will no longer cause Sync requests to fail.
79
+ 1.6.2 - 2014-07-23 - Chris Jean
80
+ Enhancement: Additional user details, such as first name and last name can be requested from the site.
81
+ 1.6.3 - 2014-08-22 - Chris Jean
82
+ Bug Fix: Caching plugins no longer prevent Sync from reporting and installing available updates.
83
+ 1.6.4 - 2014-08-25 - Chris Jean
84
+ Bug Fix: Fixed compatibility issue with the Duo Two-Factor Authentication plugin.
85
+ 1.6.5 - 2014-10-08 - Chris Jean
86
+ Enhancement: Syncing users now uses the new authentication system.
87
+ Bug Fix: Fixed warning messages being generated when unauthenticated requests are received.
88
+ 1.6.6 - 2014-10-23 - Aaron D. Campbell
89
+ Enhancement: Add get-meta, set-meta, and delete-meta actions to manage-users verb
90
+ Enhancement: Client Dashboard
91
+ 1.6.7 - 2014-10-23 - Chris Jean
92
+ Bug Fix: Fixed compatibility issues with functions blacklisted by Suhosin's suhosin.executor.func.blacklist ini setting.
93
+ 1.6.8 - 2014-10-28 - Chris Jean
94
+ Bug Fix: Fixed infinite loop issue that caused pages for logged in users to fail to load on some sites.
95
+ 1.6.9 - 2014-10-31 - Aaron D. Campbell
96
+ Bug Fix: Actual fix for possible infinite loop caused by child nodes with no valid parent in admin bar
97
+ 1.6.10 - 2014-11-10 - Aaron D. Campbell
98
+ Enhancement: Updates to Gravity Forms's verb to allow pagination and proper labeling on Sync side
99
+ 1.6.11 - 2014-11-10 - Chris Jean
100
+ Enhancement: Added an alternate contaction method using admin-ajax.php in order to avoid communication issues on some systems.
101
+ Enhancement: Improved system memory statistics for some Linux systems.
102
+ Bug Fix: Corrected a timing issue that prevented updates for some plugins from appearing in the Sync dashboard.
103
+ 1.6.12 - 2014-11-14 - Packaging Bot (lib/updater)
104
+ Enhancement: Reduced caching to allow updates to appear more quickly.
105
+ Enhancement: Authenticating users now uses the new authentication system.
106
+ 1.7.0 - 2014-11-18 - Chris Jean
107
+ Enhancement: Added the ability to manage iThemes product licenses.
108
+ 1.7.1 - 2014-11-24 - Chris Jean
109
+ Bug Fix: Data containing non-ASCII characters is now properly parsed.
110
+ 1.7.4 - 2015-03-05 - Aaron D. Campbell
111
+ Enhancement: Add get-posts and manage-options verbs
112
+ Enhancement: Added ability to integrate social sharing
113
+ 1.7.5 - 2015-04-22 - Aaron D. Campbell
114
+ Bug Fix: Client Dashboard shouldn't hide the hamburger icon used for responsive admin
115
+ 1.7.6 - 2015-04-23 - Packaging Bot (lib/updater)
116
+ Compatibility Fix: Updated plugin and theme update data format to match changes in WordPress 4.2.
117
+ 1.7.7 - 2015-06-22 Chris Jean
118
+ Bug Fix: Resolved a fatal error situation that could happen when WordPress attempts to display an error message during plugin or theme upgrades.
119
+ 1.7.8 - 2015-07-14 - Packaging Bot (lib/updater)
120
+ Enhancement: Updated link to iThemes support.
121
+ 1.7.9 - 2015-07-21 - Chris Jean
122
+ Bug Fix: An error is no longer sent when a plugin or theme updates to a newer version than the one reported.
123
+ 1.8.0 - 2015-08-18 - Chris Jean
124
+ Enhancement: Added support for translations being located outside the plugin.
125
+ Compatibility Fix: Later priorities for Sync dashboard admin menu filtering to work with certain edge-case plugins.
126
+ Bug Fix: Changed path sent to load_plugin_textdomain() in order to properly load translations.
127
+ 1.8.1 - 2015-08-24 - Chris Jean
128
+ Compatibility Fix: Added support for a define to disable a feature of Sync that sets the WP_ADMIN define to true when handling an authenticated request from the Sync server. This feature exists to prevent compatibility issues with some security plugins yet can also cause a conflict with some plugins. To disable this feature, add the following to the site's wp-config.php file: define( 'ITHEMES_SYNC_SKIP_SET_IS_ADMIN_TO_TRUE', true );
129
+ 1.8.2 - 2015-09-25 - Chris Jean
130
+ Compatibility Fix: Updated method that the Client Dashboard uses to read admin menu details. This avoids conflicts with some plugins that modify the admin menu later than expected.
131
+ 1.8.3 - 2015-11-05 - Chris Jean
132
+ Bug Fix: Data from plugins, themes, and other sources that contain non-UTF-8 data are now properly parsed into UTF-8 encoding. This prevents problems communicating between the site and the Sync server when such data are present.
133
+ 1.9.0 - 2016-04-22 - Lew Ayotte, Ty Carlson
134
+ Enhancement: Adding Core, Plugin, and Theme notices
135
+ Enhacnement: Adding more information with Plugin and Theme verbs
136
+ Compatibility Fix: Automatically add Sync Server IPs to iThemes Security whitelist
137
+ Bug Fix: Handle reporting properly when site is synced to multiple profiles
138
+ 1.10.0 - 2016-06-21 - Lew Ayotte
139
+ Enhancement: Code efficiency updates
140
+ Bug Fix: Fixing invalid path reference
141
+ 1.11.0 - 2016-07-28 - Lew Ayotte
142
+ Enhancement: Pre-Release to new Whitelabelled Reporting Verbs
143
+ Bug Fix: Stop sending empty security notices
144
+ 1.12.0 - 2016-08-24 - Lew Ayotte
145
+ Enhancement: Federated Login
146
+ 1.12.1 - 2016-08-25 - Lew Ayotte
147
+ Bug Fix: Removing debug code not needed in live code
148
+ 1.13.0 - 2016-08-25 - Lew Ayotte
149
+ Enhancement: Extending Federated Login functionality to other User Accounts
150
+ 1.14.0 - 2016-08-29 - Lew Ayotte
151
+ Improvement: Reduce amount of BackupBuddy noise being sent through to Urgent Notices.
152
+ 1.14.1 - 2016-08-29 - Lew Ayotte
153
+ Bug Fix: Typo in last commit prevent some BuB notices that are needed
154
+ 1.15.0 - 2016-09-02 - Lew Ayotte
155
+ Improvement: Extending Federated Login functionality to include subpages to wp-admin
156
+ 1.16.0 - 2016-09-28 - Lew Ayotte
157
+ Improvement: Extending comment functionality
158
+ 1.16.1 - 2016-09-29 - Lew Ayotte
159
+ Committing missing code change, no affect on code but needs to be included
160
+ 1.17.0 - 2016-11-08 - Lew Ayotte
161
+ Improvement: Posts/Pages and Yoast SEO functionality
162
+ 1.17.1 - 2016-11-08 - Lew Ayotte
163
+ Bug Fix: Removing improper action priority.
164
+ 1.17.2 - 2016-11-09 - Lew Ayotte
165
+ Bug Fix: Try to set admin user as synced user for activity actions
166
+ 1.171.2 - 2016-11-09 - Lew Ayotte
167
+ Tagging Version Number Bug
168
+ 1.172.0 - 2016-11-14 - Lew Ayotte, Glenn Ansley
169
+ Enhancement: Duplicate Posts
170
+ Enhancement: Delete Post Revisions
171
+ 1.172.1 - 2016-11-28 - Glenn Ansley
172
+ Enhancement: Database Optimizations
173
+ 1.172.2 - 2016-12-01 - Glenn Ansley
174
+ Bug Fix: Workaround to download reports when fopen or openssl are disabled.
175
+ 1.173.0 - 2017-01-05 - Lew Ayotte
176
+ Enhancement: Google Search Console/Webmaster Tools functionality
177
+ 1.173.1 - 2017-01-05 - Lew Ayotte
178
+ Enhancement: Change how we store google verification option for Search Console/Webmaster Tools
179
+ 1.174.0 - 2017-02-15 - Lew Ayotte, Glenn Ansley
180
+ Bug Fix: Fix error when itsec sends data in WP_Error format
181
+ 1.175.0 - 2017-02-15 - Glenn Ansley
182
+ Enhancement: Add ability to read and set most options found Settings pages.
183
+ 2.0.0 - 2017-06-01 - Lew Ayotte
184
+ Initial release to WordPress.org
images/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
init.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: iThemes Sync
4
+ Plugin URI: http://ithemes.com/sync
5
+ Description: Manage updates to your WordPress sites easily in one place.
6
+ Author: iThemes
7
+ Version: 2.0.0
8
+ Author URI: http://ithemes.com/
9
+ Domain Path: /lang/
10
+ iThemes Package: ithemes-sync
11
+ */
12
+
13
+ if ( ! empty( $GLOBALS['ithemes_sync_path'] ) ) {
14
+ $active_plugin_path = preg_replace( '|^' . preg_quote( ABSPATH, '|' ) . '|', '', $GLOBALS['ithemes_sync_path'] );
15
+ $this_plugin_path = preg_replace( '|^' . preg_quote( ABSPATH, '|' ) . '|', '', dirname( __FILE__ ) );
16
+
17
+ if ( $active_plugin_path != $this_plugin_path ) {
18
+ $function = 'echo \'<div class="error"><p>';
19
+ $function .= sprintf( __( 'Only one iThemes Sync plugin can be active at a time. The plugin at <code>%1$s</code> is running while the plugin at <code>%2$s</code> was skipped in order to prevent errors. Please deactivate the plugin that you do not wish to use.', 'it-l10n-ithemes-sync' ), $active_plugin_path, $this_plugin_path );
20
+ $function .= '</p></div>\';';
21
+
22
+ $function_ref = create_function( '', $function );
23
+
24
+ add_action( 'all_admin_notices', $function_ref, 0 );
25
+ }
26
+
27
+ return;
28
+ }
29
+
30
+ $GLOBALS['ithemes_sync_path'] = dirname( __FILE__ );
31
+
32
+ require( $GLOBALS['ithemes_sync_path'] . '/load.php' );
33
+
34
+ /**
35
+ * On activation, set a time, frequency and name of an action hook to be scheduled.
36
+ *
37
+ * @since 1.12.0
38
+ */
39
+ function ithemes_sync_activation() {
40
+ if ( ! wp_next_scheduled ( 'ithemes_sync_daily_schedule' ) ) {
41
+ wp_schedule_event( strtotime( 'Tomorrow 2AM' ), 'daily', 'ithemes_sync_daily_schedule' );
42
+ }
43
+ }
44
+ register_activation_hook( __FILE__, 'ithemes_sync_activation' );
45
+
46
+ /**
47
+ * On deactivation, remove all functions from the scheduled action hook.
48
+ *
49
+ * @since 1.12.0
50
+ */
51
+ function ithemes_sync_deactivation() {
52
+ wp_clear_scheduled_hook( 'ithemes_sync_daily_schedule' );
53
+ }
54
+ register_deactivation_hook( __FILE__, 'ithemes_sync_deactivation' );
js/admin-notice.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(document).ready(
2
+ function() {
3
+ jQuery('.ithemes-sync-notice-dismiss').click(
4
+ function( e ) {
5
+ jQuery('#ithemes-sync-notice').hide();
6
+
7
+ return false;
8
+ }
9
+ );
10
+
11
+ jQuery('.ithemes-sync-notice-hide').click(
12
+ function( e ) {
13
+ jQuery('#ithemes-sync-notice').hide();
14
+
15
+ var post_data = {
16
+ action: 'ithemes_sync_hide_notice'
17
+ };
18
+
19
+ jQuery.post( ajaxurl, post_data );
20
+
21
+ return false;
22
+ }
23
+ );
24
+ }
25
+ );
js/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
js/settings-page.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(document).ready(
2
+ function() {
3
+ jQuery('.ithemes-sync-wrapper .deauthenticate').click(
4
+ function( event ) {
5
+ var response = confirm( ithemes_sync_settings.confirm_dialog_text );
6
+ return response;
7
+ }
8
+ );
9
+ }
10
+ );
js/social-metabox.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function( $ ) {
2
+
3
+ var itSyncSocial = {
4
+
5
+ init: function() {
6
+ this.bindEvents();
7
+
8
+ /** The account list is not shown after post is published, so only call the initial character count method if list exists **/
9
+ if ( $('.ithemes-social-accounts').length ) {
10
+ this.updateCharCount( $('.ithemes-social-account-content textarea') );
11
+ }
12
+ },
13
+
14
+ bindEvents: function() {
15
+ $('#submitpost').on('click', '.ithemes-social-edit-content', this.toggleEdit);
16
+ $('#submitpost').on('keyup', '.ithemes-social-account-content textarea', this.onKeyup);
17
+ },
18
+
19
+ toggleEdit: function(event) {
20
+ event.preventDefault();
21
+ $(this).siblings('.ithemes-social-account-content').slideToggle('fast');
22
+ },
23
+
24
+ onKeyup: function(event) {
25
+ itSyncSocial.updateCharCount(event.currentTarget);
26
+ },
27
+
28
+ updateCharCount: function(element) {
29
+ var content = $(element).val()
30
+ .replace( /\{post_author\}/, jQuery('#post_author_override option:selected').text() )
31
+ .replace( /\{post_title\}/, jQuery('#title').val() )
32
+ .replace( /\{post_url\}/, '{post_url-twentythree-}' ); //URLs are 23 characters, so we're padding this to that
33
+
34
+ var container = $(element).siblings('.ithemes-social-char-count'),
35
+ currentLength = content.length,
36
+ remaining = 140 - currentLength;
37
+ isOver = remaining <= 0 ? true : false;
38
+
39
+ container.html(remaining);
40
+ isOver ? container.addClass('over') : container.removeClass('over');
41
+ }
42
+
43
+ };
44
+
45
+ jQuery(document).ready( function() {
46
+ itSyncSocial.init();
47
+ });
48
+
49
+ })( jQuery );
lang/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
lang/ithemes-sync.pot ADDED
@@ -0,0 +1,620 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2017 iThemes Sync
2
+ # This file is distributed under the same license as the iThemes Sync package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: iThemes Sync 2.0.0\n"
6
+ "Report-Msgid-Bugs-To: http://ithemes.com/support/\n"
7
+ "POT-Creation-Date: 2017-06-01 20:02:57+00:00\n"
8
+ "PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n"
9
+ "MIME-Version: 1.0\n"
10
+ "Content-Type: text/plain; charset=UTF-8\n"
11
+ "Content-Transfer-Encoding: 8bit\n"
12
+ "Plural-Forms: nplurals=2; plural=n != 1;\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+
16
+ #: admin.php:118
17
+ msgid ""
18
+ "iThemes Sync is active. <a class=\"ithemes-sync-notice-button\" href=\"%s"
19
+ "\">Manage Sync</a> <a class=\"ithemes-sync-notice-dismiss\" href=\"#\">×</a>"
20
+ msgstr ""
21
+
22
+ #: admin.php:132
23
+ msgid ""
24
+ "iThemes Sync is almost ready. <a class=\"ithemes-sync-notice-button\" href="
25
+ "\"%s\">Set Up Sync</a> <a class=\"ithemes-sync-notice-hide\" href=\"#\">×</a>"
26
+ msgstr ""
27
+
28
+ #: admin.php:147
29
+ msgid ""
30
+ "iThemes Sync will show for your user for the next %1$s. Click <a href=\"%2$s"
31
+ "\">here</a> to hide iThemes Sync again."
32
+ msgstr ""
33
+
34
+ #: admin.php:157
35
+ msgid "iThemes Sync is now hidden from your user again."
36
+ msgstr ""
37
+
38
+ #. #-#-#-#-# ithemes-sync.pot (iThemes Sync 2.0.0) #-#-#-#-#
39
+ #. Plugin Name of the plugin/theme
40
+ #: admin.php:168 admin.php:174 settings-page.php:268
41
+ msgid "iThemes Sync"
42
+ msgstr ""
43
+
44
+ #: admin.php:202 lib/updater/admin.php:181
45
+ msgid "Manage iThemes product licenses to receive automatic upgrade support"
46
+ msgstr ""
47
+
48
+ #: admin.php:202 lib/updater/admin.php:181
49
+ msgid "License"
50
+ msgstr ""
51
+
52
+ #: client-dashboard.php:350
53
+ msgctxt "Welcome panel"
54
+ msgid "Welcome"
55
+ msgstr ""
56
+
57
+ #: duplicator.php:31
58
+ msgid "Duplicate Post"
59
+ msgstr ""
60
+
61
+ #: duplicator.php:71
62
+ msgid "copy"
63
+ msgstr ""
64
+
65
+ #: init.php:19
66
+ msgid ""
67
+ "Only one iThemes Sync plugin can be active at a time. The plugin at <code>"
68
+ "%1$s</code> is running while the plugin at <code>%2$s</code> was skipped in "
69
+ "order to prevent errors. Please deactivate the plugin that you do not wish "
70
+ "to use."
71
+ msgstr ""
72
+
73
+ #: lib/updater/admin.php:140
74
+ msgid ""
75
+ "To receive automatic updates for %l, use the <a href=\"%s\">iThemes "
76
+ "Licensing</a> page found in the Settings menu."
77
+ msgstr ""
78
+
79
+ #: lib/updater/admin.php:147 lib/updater/admin.php:153
80
+ #: lib/updater/settings-page.php:302
81
+ msgid "iThemes Licensing"
82
+ msgstr ""
83
+
84
+ #: lib/updater/api.php:41 lib/updater/api.php:59
85
+ msgid ""
86
+ "<p>Unable to get changelog data at this time.</p><p>Error <code>%1$s</code>: "
87
+ "%2$s</p>"
88
+ msgstr ""
89
+
90
+ #: lib/updater/api.php:45
91
+ msgid ""
92
+ "<p>Unable to get changelog data at this time.</p><p>Error: Unrecognized "
93
+ "response from <code>wp_remote_get</code>.</p>"
94
+ msgstr ""
95
+
96
+ #: lib/updater/api.php:49
97
+ msgid ""
98
+ "<p>Unable to get changelog data at this time.</p><p>Error code <code>%1$s</"
99
+ "code>: %2$s</p>"
100
+ msgstr ""
101
+
102
+ #: lib/updater/api.php:61
103
+ msgid ""
104
+ "<p>Unable to get changelog data at this time.</p><p>Error: Unrecognized "
105
+ "response from iThemes API server.</p>"
106
+ msgstr ""
107
+
108
+ #: lib/updater/server.php:78
109
+ msgid ""
110
+ "The server could not be contacted. Requests are delayed for %d minute to "
111
+ "allow the server to recover."
112
+ msgid_plural ""
113
+ "The server could not be contacted. Requests are delayed for %d minutes to "
114
+ "allow the server to recover."
115
+ msgstr[0] ""
116
+ msgstr[1] ""
117
+
118
+ #: lib/updater/server.php:155
119
+ msgid "The server was unable to be contacted."
120
+ msgstr ""
121
+
122
+ #: lib/updater/server.php:165
123
+ msgid ""
124
+ "An error occurred when communicating with the iThemes update server: %s (%s)"
125
+ msgstr ""
126
+
127
+ #: lib/updater/settings-page.php:77
128
+ msgid ""
129
+ "Incorrect password. Please make sure that you are supplying your iThemes "
130
+ "membership username and password details."
131
+ msgstr ""
132
+
133
+ #: lib/updater/settings-page.php:81
134
+ msgid ""
135
+ "Invalid username. Please make sure that you are supplying your iThemes "
136
+ "membership username and password details."
137
+ msgstr ""
138
+
139
+ #: lib/updater/settings-page.php:84
140
+ msgid ""
141
+ "The licensing server reports that the %1$s (%2$s) product is unknown. Please "
142
+ "contact support for assistance."
143
+ msgstr ""
144
+
145
+ #: lib/updater/settings-page.php:87
146
+ msgid ""
147
+ "%1$s could not be licensed since the membership account is out of available "
148
+ "licenses for this product. You can unlicense the product on other sites or "
149
+ "upgrade your membership to one with a higher number of licenses in order to "
150
+ "increase the amount of available licenses."
151
+ msgstr ""
152
+
153
+ #: lib/updater/settings-page.php:90
154
+ msgid ""
155
+ "%s could not be licensed due to an internal error. Please try to license %s "
156
+ "again at a later time. If this problem continues, please contact iThemes "
157
+ "support."
158
+ msgstr ""
159
+
160
+ #: lib/updater/settings-page.php:96
161
+ msgid ""
162
+ "An unknown error relating to the %1$s product occurred. Please contact "
163
+ "iThemes support. Error details: %2$s"
164
+ msgstr ""
165
+
166
+ #: lib/updater/settings-page.php:98
167
+ msgid ""
168
+ "An unknown error occurred. Please contact iThemes support. Error details: %s"
169
+ msgstr ""
170
+
171
+ #: lib/updater/settings-page.php:129 settings-page.php:239
172
+ msgid "Settings saved"
173
+ msgstr ""
174
+
175
+ #: lib/updater/settings-page.php:136
176
+ msgid ""
177
+ "You must supply an iThemes membership username and password in order to "
178
+ "license products."
179
+ msgstr ""
180
+
181
+ #: lib/updater/settings-page.php:138
182
+ msgid ""
183
+ "You must supply an iThemes membership username in order to license products."
184
+ msgstr ""
185
+
186
+ #: lib/updater/settings-page.php:140
187
+ msgid ""
188
+ "You must supply an iThemes membership password in order to license products."
189
+ msgstr ""
190
+
191
+ #: lib/updater/settings-page.php:142
192
+ msgid ""
193
+ "You must select at least one product to license. Ensure that you select the "
194
+ "products that you wish to license in the listing below."
195
+ msgstr ""
196
+
197
+ #: lib/updater/settings-page.php:157
198
+ msgid ""
199
+ "An unknown server error occurred. Please try to license your products again "
200
+ "at another time."
201
+ msgstr ""
202
+
203
+ #: lib/updater/settings-page.php:177
204
+ msgid "Your product subscription has expired"
205
+ msgstr ""
206
+
207
+ #: lib/updater/settings-page.php:184
208
+ msgid "Successfully licensed %l."
209
+ msgstr ""
210
+
211
+ #: lib/updater/settings-page.php:188 lib/updater/settings-page.php:193
212
+ msgid "Unable to license %1$s. Reason: %2$s"
213
+ msgstr ""
214
+
215
+ #: lib/updater/settings-page.php:201
216
+ msgid ""
217
+ "You must supply an iThemes membership username and password in order to "
218
+ "remove licenses."
219
+ msgstr ""
220
+
221
+ #: lib/updater/settings-page.php:203
222
+ msgid ""
223
+ "You must supply an iThemes membership username in order to remove licenses."
224
+ msgstr ""
225
+
226
+ #: lib/updater/settings-page.php:205
227
+ msgid ""
228
+ "You must supply an iThemes membership password in order to remove licenses."
229
+ msgstr ""
230
+
231
+ #: lib/updater/settings-page.php:207
232
+ msgid ""
233
+ "You must select at least one license to remove. Ensure that you select the "
234
+ "licenses that you wish to remove in the listing below."
235
+ msgstr ""
236
+
237
+ #: lib/updater/settings-page.php:222
238
+ msgid ""
239
+ "An unknown server error occurred. Please try to remove licenses from your "
240
+ "products again at another time."
241
+ msgstr ""
242
+
243
+ #: lib/updater/settings-page.php:243
244
+ msgid "Unknown server error."
245
+ msgstr ""
246
+
247
+ #: lib/updater/settings-page.php:248
248
+ msgid "Successfully removed license from %l."
249
+ msgid_plural "Successfully removed licenses from %l."
250
+ msgstr[0] ""
251
+ msgstr[1] ""
252
+
253
+ #: lib/updater/settings-page.php:252
254
+ msgid "Unable to remove license from %1$s. Reason: %2$s"
255
+ msgstr ""
256
+
257
+ #: lib/updater/settings-page.php:324
258
+ msgid "Settings"
259
+ msgstr ""
260
+
261
+ #: lib/updater/settings-page.php:330
262
+ msgid "Quick Release Updates"
263
+ msgstr ""
264
+
265
+ #: lib/updater/settings-page.php:337
266
+ msgid "Enable quick release updates"
267
+ msgstr ""
268
+
269
+ #: lib/updater/settings-page.php:340
270
+ msgid ""
271
+ "Some products have quick releases that are created to solve specific issues "
272
+ "that some users experience. In order to avoid causing users to have updates "
273
+ "show up too frequently, automatic updates to these quick releases are "
274
+ "disabled by default. Enabling this feature allows quick releases to be "
275
+ "available to the automatic update system. Using this option is only "
276
+ "recommended if support has requested that you enable it in order to receive "
277
+ "a quick release. You should disable this option at a later time after "
278
+ "confirming that the quick release solves the issue for you."
279
+ msgstr ""
280
+
281
+ #: lib/updater/settings-page.php:347
282
+ msgid "Save Settings"
283
+ msgstr ""
284
+
285
+ #: lib/updater/settings-page.php:366 lib/updater/settings-page.php:397
286
+ #: lib/updater/settings-page.php:521 lib/updater/settings-page.php:589
287
+ msgid "Product"
288
+ msgstr ""
289
+
290
+ #: lib/updater/settings-page.php:367 lib/updater/settings-page.php:399
291
+ msgid "Member"
292
+ msgstr ""
293
+
294
+ #: lib/updater/settings-page.php:368 lib/updater/settings-page.php:401
295
+ msgid "Expiration"
296
+ msgstr ""
297
+
298
+ #: lib/updater/settings-page.php:369 lib/updater/settings-page.php:402
299
+ msgid "Remaining Licenses"
300
+ msgstr ""
301
+
302
+ #: lib/updater/settings-page.php:385
303
+ msgid "Licensed Products"
304
+ msgstr ""
305
+
306
+ #: lib/updater/settings-page.php:391 lib/updater/settings-page.php:515
307
+ msgid "Select All"
308
+ msgstr ""
309
+
310
+ #: lib/updater/settings-page.php:400
311
+ msgid "Product Status"
312
+ msgstr ""
313
+
314
+ #: lib/updater/settings-page.php:410
315
+ msgid "unlimited"
316
+ msgstr ""
317
+
318
+ #: lib/updater/settings-page.php:451 lib/updater/settings-page.php:545
319
+ msgid "Select %s"
320
+ msgstr ""
321
+
322
+ #: lib/updater/settings-page.php:471
323
+ msgid "Remove Licenses"
324
+ msgstr ""
325
+
326
+ #: lib/updater/settings-page.php:505
327
+ msgid "Unlicensed Products"
328
+ msgstr ""
329
+
330
+ #: lib/updater/settings-page.php:507
331
+ msgid ""
332
+ "The following products have not been licensed. Licensing a product gives you "
333
+ "access to automatic updates from within WordPress."
334
+ msgstr ""
335
+
336
+ #: lib/updater/settings-page.php:508
337
+ msgid ""
338
+ "To license products, select the products you wish to license, enter your "
339
+ "iThemes membership username and password, and press the License Products "
340
+ "button."
341
+ msgstr ""
342
+
343
+ #: lib/updater/settings-page.php:509
344
+ msgid "Need help? <a href=\"%s\">Click here for a quick video tutorial</a>."
345
+ msgstr ""
346
+
347
+ #: lib/updater/settings-page.php:561
348
+ msgid "License Products"
349
+ msgstr ""
350
+
351
+ #: lib/updater/settings-page.php:581
352
+ msgid "Unrecognized Products"
353
+ msgstr ""
354
+
355
+ #: lib/updater/settings-page.php:583
356
+ msgid ""
357
+ "The following products were not recognized by the licensing system. This can "
358
+ "be due to a bug in the product code, a temporary server issue, or because "
359
+ "the product is no longer supported."
360
+ msgstr ""
361
+
362
+ #: lib/updater/settings-page.php:584
363
+ msgid ""
364
+ "Please check this page again at a later time to see if the problem resolves "
365
+ "itself. If the product remains, please contact <a href=\"%s\">iThemes "
366
+ "support</a> and provide them with the details given below."
367
+ msgstr ""
368
+
369
+ #: lib/updater/settings-page.php:590
370
+ msgid "Type"
371
+ msgstr ""
372
+
373
+ #: lib/updater/settings-page.php:591
374
+ msgid "Package"
375
+ msgstr ""
376
+
377
+ #: lib/updater/settings-page.php:592
378
+ msgid "Version"
379
+ msgstr ""
380
+
381
+ #: lib/updater/settings-page.php:593
382
+ msgid "Server Response"
383
+ msgstr ""
384
+
385
+ #: lib/updater/settings-page.php:603
386
+ msgid "Unknown Error"
387
+ msgstr ""
388
+
389
+ #: lib/updater/settings-page.php:642
390
+ msgid "%d day"
391
+ msgid_plural "%d days"
392
+ msgstr[0] ""
393
+ msgstr[1] ""
394
+
395
+ #: lib/updater/settings-page.php:644
396
+ msgid "%d hour"
397
+ msgid_plural "%d hours"
398
+ msgstr[0] ""
399
+ msgstr[1] ""
400
+
401
+ #: lib/updater/settings-page.php:646
402
+ msgid "%d minute"
403
+ msgid_plural "%d minutes"
404
+ msgstr[0] ""
405
+ msgstr[1] ""
406
+
407
+ #: lib/updater/settings-page.php:648
408
+ msgid "%d second"
409
+ msgid_plural "%d seconds"
410
+ msgstr[0] ""
411
+ msgstr[1] ""
412
+
413
+ #: lib/updater/settings-page.php:651
414
+ msgid "%s ago"
415
+ msgstr ""
416
+
417
+ #: notice-handler.php:89
418
+ msgid "The Sync server returned an unknown response."
419
+ msgstr ""
420
+
421
+ #: server.php:188
422
+ msgid ""
423
+ "An unrecognized server response format was received from the iThemes Sync "
424
+ "server."
425
+ msgstr ""
426
+
427
+ #: settings-page.php:54
428
+ msgid "Are you sure that you wish to unsync this user?"
429
+ msgstr ""
430
+
431
+ #: settings-page.php:91 settings-page.php:108 settings-page.php:117
432
+ #: settings-page.php:129
433
+ msgid "The user could not be synced."
434
+ msgstr ""
435
+
436
+ #: settings-page.php:96
437
+ msgid ""
438
+ "<p>The iThemes Sync server was unable to be contacted. WordPress returned "
439
+ "the following error when trying to contact the server:</p><p>%1$s</p><p>If "
440
+ "you continue to experience problems, please contact <a target=\"_blank\" "
441
+ "href=\"%2$s\">iThemes support</a>.</p>"
442
+ msgstr ""
443
+
444
+ #: settings-page.php:98
445
+ msgid ""
446
+ "<p>The iThemes Sync server was unable to process the request at this time. "
447
+ "Please try again in a few minutes.</p><p>If you continue to experience "
448
+ "problems, please contact <a target=\"_blank\" href=\"%s\">iThemes support</"
449
+ "a>.</p>"
450
+ msgstr ""
451
+
452
+ #: settings-page.php:109
453
+ msgid ""
454
+ "The sync request failed due to a server communication error. The server sent "
455
+ "a response that did not include a authentication key."
456
+ msgstr ""
457
+
458
+ #: settings-page.php:118
459
+ msgid ""
460
+ "The sync request failed due to a server communication error. The server sent "
461
+ "a response that did not include a user ID."
462
+ msgstr ""
463
+
464
+ #: settings-page.php:138
465
+ msgid "Woohoo! Your site has been synced."
466
+ msgstr ""
467
+
468
+ #: settings-page.php:142
469
+ msgid ""
470
+ "Your user has now used all of the sites available to be added to Sync. More "
471
+ "information can be found on the <a href=\"%s\" target=\"_blank\">iThemes "
472
+ "membership panel</a>."
473
+ msgstr ""
474
+
475
+ #: settings-page.php:144
476
+ msgid "Your user can Sync 1 additional site."
477
+ msgid_plural "Your user can sync %d additional sites."
478
+ msgstr[0] ""
479
+ msgstr[1] ""
480
+
481
+ #: settings-page.php:160 settings-page.php:172
482
+ msgid "The user could not be unsynced."
483
+ msgstr ""
484
+
485
+ #: settings-page.php:181
486
+ msgid "The user was successfully unsynced."
487
+ msgstr ""
488
+
489
+ #: settings-page.php:266
490
+ msgid "Sync This Site"
491
+ msgstr ""
492
+
493
+ #: settings-page.php:276
494
+ msgid "Go Manage Your Synced Sites"
495
+ msgstr ""
496
+
497
+ #: settings-page.php:279
498
+ msgid "Manage Synced Users"
499
+ msgstr ""
500
+
501
+ #: settings-page.php:281
502
+ msgid ""
503
+ "Sync allows you to sync your site with multiple users.<br>View the list of "
504
+ "synced users below, unsync users if needed, or add additional users below."
505
+ msgstr ""
506
+
507
+ #: settings-page.php:285
508
+ msgid "Synced Users"
509
+ msgstr ""
510
+
511
+ #: settings-page.php:300
512
+ msgid "Invalid Users"
513
+ msgstr ""
514
+
515
+ #: settings-page.php:302
516
+ msgid ""
517
+ "The following users were not recognized by the server. Unsync them and "
518
+ "resync them again to fix this error."
519
+ msgstr ""
520
+
521
+ #: settings-page.php:320
522
+ msgid "Enter your iThemes username & password to sync this site."
523
+ msgstr ""
524
+
525
+ #: settings-page.php:322
526
+ msgid "Add Users"
527
+ msgstr ""
528
+
529
+ #: settings-page.php:323
530
+ msgid ""
531
+ "Add additional users if more than one person will be managing updates for "
532
+ "this site.<br>To sync an additional user, enter their iThemes username and "
533
+ "password below."
534
+ msgstr ""
535
+
536
+ #: settings-page.php:327
537
+ msgid "Username"
538
+ msgstr ""
539
+
540
+ #: settings-page.php:330
541
+ msgid "Password"
542
+ msgstr ""
543
+
544
+ #: settings-page.php:333
545
+ msgid "Sync"
546
+ msgstr ""
547
+
548
+ #: settings.php:131
549
+ msgid ""
550
+ "The user is not authenticated. Could not remove authentication for the user."
551
+ msgstr ""
552
+
553
+ #: settings.php:197
554
+ msgid ""
555
+ "A valid user was unable to be found. A valid user is required in order to do "
556
+ "a successful ping."
557
+ msgstr ""
558
+
559
+ #: social.php:86
560
+ msgid "Social"
561
+ msgstr ""
562
+
563
+ #: social.php:102
564
+ msgid "This post is already published."
565
+ msgstr ""
566
+
567
+ #: social.php:119
568
+ msgid "Edit"
569
+ msgstr ""
570
+
571
+ #: social.php:123
572
+ msgid "Template Tags"
573
+ msgstr ""
574
+
575
+ #: verbs/db-optimization.php:96
576
+ msgid "Delete Trackback URLs"
577
+ msgstr ""
578
+
579
+ #: verbs/db-optimization.php:97
580
+ msgid ""
581
+ "Deletes URLs added to the \"pinged\" and \"to_ping\" columns in your posts "
582
+ "table."
583
+ msgstr ""
584
+
585
+ #: verbs/db-optimization.php:382
586
+ msgid "Never"
587
+ msgstr ""
588
+
589
+ #: verbs/get-wordpress-settings.php:141
590
+ msgctxt "timezone date format"
591
+ msgid "Y-m-d H:i:s"
592
+ msgstr ""
593
+
594
+ #: verbs/get-wordpress-settings.php:184
595
+ msgid "F j, Y"
596
+ msgstr ""
597
+
598
+ #: verbs/get-wordpress-settings.php:198
599
+ msgid "g:i a"
600
+ msgstr ""
601
+
602
+ #: verbs/get-wordpress-settings.php:239 verbs/get-wordpress-settings.php:246
603
+ msgid "&mdash; Select &mdash;"
604
+ msgstr ""
605
+
606
+ #. Plugin URI of the plugin/theme
607
+ msgid "http://ithemes.com/sync"
608
+ msgstr ""
609
+
610
+ #. Description of the plugin/theme
611
+ msgid "Manage updates to your WordPress sites easily in one place."
612
+ msgstr ""
613
+
614
+ #. Author of the plugin/theme
615
+ msgid "iThemes"
616
+ msgstr ""
617
+
618
+ #. Author URI of the plugin/theme
619
+ msgid "http://ithemes.com/"
620
+ msgstr ""
lib/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
lib/updater/admin.php ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Set up admin interface elements.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.2
7
+
8
+ Version History
9
+ 1.0.0 - 2013-09-19 - Chris Jean
10
+ Split off from the old Ithemes_Updater_Init class.
11
+ 1.1.0 - 2013-10-02 - Chris Jean
12
+ Added support for themes through the filter_plugins_api function (since themes don't have a "View version *** details" feature.
13
+ 1.2.0 - 2013-10-23 - Chris Jean
14
+ Changed how the licensing page is registered for multisite. It now will only load on multisite sites if the user is a super user (network admin).
15
+ Removed the code that handled the setting to show or hide the licensing page on multisite sites.
16
+ 1.2.1 - 2013-10-25 - Chris Jean
17
+ Added "License" links to Network Admin Plugins and Themes pages.
18
+ 1.2.2 - 2014-10-23 - Chris Jean
19
+ Updated code formating to WordPress coding standards.
20
+ */
21
+
22
+
23
+ class Ithemes_Updater_Admin {
24
+ private $page_name = 'ithemes-licensing';
25
+
26
+ private $package_details = false;
27
+ private $registration_link = false;
28
+
29
+ private $page_ref;
30
+
31
+
32
+ public function __construct() {
33
+ require_once( $GLOBALS['ithemes_updater_path'] . '/settings.php' );
34
+
35
+ if ( ! is_multisite() || is_super_admin() ) {
36
+ add_action( 'admin_menu', array( $this, 'add_admin_pages' ) );
37
+ }
38
+
39
+ add_action( 'network_admin_menu', array( $this, 'add_network_admin_pages' ) );
40
+
41
+ add_action( 'admin_head-plugins.php', array( $this, 'show_activation_message' ) );
42
+ add_action( 'admin_head-themes.php', array( $this, 'show_activation_message' ) );
43
+ add_action( 'deactivated_plugin', array( $this, 'clear_activation_package' ) );
44
+
45
+ add_filter( 'upgrader_pre_install', array( $this, 'filter_upgrader_pre_install' ) );
46
+ add_filter( 'upgrader_post_install', array( $this, 'filter_upgrader_post_install' ), 10, 3 );
47
+ add_filter( 'plugins_api', array( $this, 'filter_plugins_api' ), 10, 3 );
48
+
49
+ if ( ! is_multisite() || is_super_admin() ) {
50
+ add_filter( 'plugin_action_links', array( $this, 'filter_plugin_action_links' ), 10, 4 );
51
+ add_filter( 'theme_action_links', array( $this, 'filter_theme_action_links' ), 10, 2 );
52
+ }
53
+
54
+ add_filter( 'network_admin_plugin_action_links', array( $this, 'filter_plugin_action_links' ), 10, 4 );
55
+ add_filter( 'network_admin_theme_action_links', array( $this, 'filter_theme_action_links' ), 10, 2 );
56
+ }
57
+
58
+ public function filter_plugins_api( $value, $action, $args ) {
59
+ $options = $GLOBALS['ithemes-updater-settings']->get_options();
60
+
61
+ if ( ! isset( $args->slug ) ) {
62
+ return $value;
63
+ }
64
+
65
+ foreach ( (array) $options['update_plugins'] as $path => $data ) {
66
+ if ( $data->slug == $args->slug ) {
67
+ require_once( $GLOBALS['ithemes_updater_path'] . '/information.php' );
68
+ return Ithemes_Updater_Information::get_plugin_information( $path );
69
+ }
70
+ }
71
+
72
+ foreach ( (array) $options['update_themes'] as $path => $data ) {
73
+ if ( $path == $args->slug ) {
74
+ require_once( $GLOBALS['ithemes_updater_path'] . '/information.php' );
75
+ return Ithemes_Updater_Information::get_theme_information( $path );
76
+ }
77
+ }
78
+
79
+ return $value;
80
+ }
81
+
82
+ public function filter_upgrader_pre_install( $value ) {
83
+ $this->set_package_details();
84
+
85
+ return $value;
86
+ }
87
+
88
+ public function filter_upgrader_post_install( $value, $hook_extra, $result ) {
89
+ $options = $GLOBALS['ithemes-updater-settings']->queue_flush();
90
+
91
+ return $value;
92
+ }
93
+
94
+ public function clear_activation_package( $deactivated_path ) {
95
+ $packages = $GLOBALS['ithemes-updater-settings']->get_packages();
96
+ $options = $GLOBALS['ithemes-updater-settings']->get_options();
97
+
98
+ $deactivated_path = WP_PLUGIN_DIR . "/$deactivated_path";
99
+
100
+ foreach ( $packages as $package => $paths ) {
101
+ if ( ! in_array( $deactivated_path, $paths ) || ( count( $paths ) > 1 ) ) {
102
+ continue;
103
+ }
104
+
105
+ $index = array_search( $package, $options['packages'] );
106
+
107
+ if ( false === $index ) {
108
+ return;
109
+ }
110
+
111
+ unset( $options['packages'][$index] );
112
+ $GLOBALS['ithemes-updater-settings']->update_options( $options );
113
+
114
+ return;
115
+ }
116
+ }
117
+
118
+ public function show_activation_message() {
119
+ $new_packages = $GLOBALS['ithemes-updater-settings']->get_new_packages();
120
+
121
+ if ( empty( $new_packages ) ) {
122
+ return;
123
+ }
124
+
125
+
126
+ natcasesort( $new_packages );
127
+ require_once( $GLOBALS['ithemes_updater_path'] . '/functions.php' );
128
+ $names = array();
129
+
130
+ foreach ( $new_packages as $package ) {
131
+ $names = Ithemes_Updater_Functions::get_package_name( $package );
132
+ }
133
+
134
+ if ( is_multisite() && is_network_admin() ) {
135
+ $url = network_admin_url( 'settings.php' ) . "?page={$this->page_name}";
136
+ } else {
137
+ $url = admin_url( 'options-general.php' ) . "?page={$this->page_name}";
138
+ }
139
+
140
+ echo '<div class="updated fade"><p>' . wp_sprintf( __( 'To receive automatic updates for %l, use the <a href="%s">iThemes Licensing</a> page found in the Settings menu.', 'it-l10n-ithemes-sync' ), $names, $url ) . '</p></div>';
141
+
142
+
143
+ $GLOBALS['ithemes-updater-settings']->update_packages();
144
+ }
145
+
146
+ public function add_admin_pages() {
147
+ $this->page_ref = add_options_page( __( 'iThemes Licensing', 'it-l10n-ithemes-sync' ), __( 'iThemes Licensing', 'it-l10n-ithemes-sync' ), 'manage_options', $this->page_name, array( $this, 'settings_index' ) );
148
+
149
+ add_action( "load-{$this->page_ref}", array( $this, 'load_settings_page' ) );
150
+ }
151
+
152
+ public function add_network_admin_pages() {
153
+ $this->page_ref = add_submenu_page( 'settings.php', __( 'iThemes Licensing', 'it-l10n-ithemes-sync' ), __( 'iThemes Licensing', 'it-l10n-ithemes-sync' ), 'manage_options', $this->page_name, array( $this, 'settings_index' ) );
154
+
155
+ add_action( "load-{$this->page_ref}", array( $this, 'load_settings_page' ) );
156
+ }
157
+
158
+ public function load_settings_page() {
159
+ require( $GLOBALS['ithemes_updater_path'] . '/settings-page.php' );
160
+ }
161
+
162
+ public function settings_index() {
163
+ do_action( 'ithemes_updater_settings_page_index' );
164
+ }
165
+
166
+ private function set_package_details() {
167
+ if ( false !== $this->package_details ) {
168
+ return;
169
+ }
170
+
171
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
172
+ $this->package_details = Ithemes_Updater_Packages::get_local_details();
173
+ }
174
+
175
+ private function set_registration_link() {
176
+ if ( false !== $this->registration_link ) {
177
+ return;
178
+ }
179
+
180
+ $url = admin_url( 'options-general.php' ) . "?page={$this->page_name}";
181
+ $this->registration_link = sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', $url, __( 'Manage iThemes product licenses to receive automatic upgrade support', 'it-l10n-ithemes-sync' ), __( 'License', 'it-l10n-ithemes-sync' ) );
182
+ }
183
+
184
+ public function filter_plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) {
185
+ $this->set_package_details();
186
+ $this->set_registration_link();
187
+
188
+ if ( isset( $this->package_details[$plugin_file] ) ) {
189
+ $actions[] = $this->registration_link;
190
+ }
191
+
192
+ return $actions;
193
+ }
194
+
195
+ public function filter_theme_action_links( $actions, $theme ) {
196
+ $this->set_package_details();
197
+ $this->set_registration_link();
198
+
199
+ if ( is_object( $theme ) ) {
200
+ $path = basename( $theme->get_stylesheet_directory() ) . '/style.css';
201
+ } else if ( is_array( $theme ) && isset( $theme['Stylesheet Dir'] ) ) {
202
+ $path = $theme['Stylesheet Dir'] . '/style.css';
203
+ } else {
204
+ $path = '';
205
+ }
206
+
207
+ if ( isset( $this->package_details[$path] ) ) {
208
+ $actions[] = $this->registration_link;
209
+ }
210
+
211
+ return $actions;
212
+ }
213
+ }
214
+
215
+ new Ithemes_Updater_Admin();
lib/updater/api.php ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Provides an easy to use interface for communicating with the iThemes updater server.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.1
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ 1.0.1 - 2013-09-19 - Chris Jean
12
+ Updated requires to not use dirname().
13
+ Updated ithemes-updater-object to ithemes-updater-settings.
14
+ 1.1.0 - 2013-10-02 - Chris Jean
15
+ Added get_package_changelog().
16
+ 1.1.1 - 2014-11-13 - Chris Jean
17
+ Improved caching.
18
+ Updated code to meet WordPress coding standards.
19
+ */
20
+
21
+
22
+ class Ithemes_Updater_API {
23
+ public static function activate_package( $username, $password, $packages ) {
24
+ return self::get_response( 'activate_package', compact( 'username', 'password', 'packages' ), false );
25
+ }
26
+
27
+ public static function deactivate_package( $username, $password, $packages ) {
28
+ return self::get_response( 'deactivate_package', compact( 'username', 'password', 'packages' ), false );
29
+ }
30
+
31
+ public static function get_package_details( $cache = true ) {
32
+ $packages = array();
33
+
34
+ return self::get_response( 'get_package_details', compact( 'packages' ), $cache );
35
+ }
36
+
37
+ public static function get_package_changelog( $package, $cur_version = false ) {
38
+ $response = wp_remote_get( 'http://api.ithemes.com/product/changelog?package=' . urlencode( $package ) );
39
+
40
+ if ( is_wp_error( $response ) ) {
41
+ return sprintf( __( '<p>Unable to get changelog data at this time.</p><p>Error <code>%1$s</code>: %2$s</p>', 'it-l10n-ithemes-sync' ), $response->get_error_code(), $response->get_error_message() );
42
+ }
43
+
44
+ if ( ! is_array( $response ) || ! isset( $response['body'] ) ) {
45
+ return __( '<p>Unable to get changelog data at this time.</p><p>Error: Unrecognized response from <code>wp_remote_get</code>.</p>', 'it-l10n-ithemes-sync' );
46
+ }
47
+
48
+ if ( isset( $response['response']['code'] ) && ( '200' != $response['response']['code'] ) ) {
49
+ return sprintf( __( '<p>Unable to get changelog data at this time.</p><p>Error code <code>%1$s</code>: %2$s</p>', 'it-l10n-ithemes-sync' ), $response['response']['code'], $response['response']['message'] );
50
+ }
51
+
52
+
53
+ $body = $response['body'];
54
+
55
+ if ( '{' === substr( $body, 0, 1 ) ) {
56
+ $error = json_decode( $body, true );
57
+
58
+ if ( is_array( $error ) && isset( $error['error'] ) && is_array( $error['error'] ) && isset( $error['error']['type'] ) && isset( $error['error']['message'] ) ) {
59
+ return sprintf( __( '<p>Unable to get changelog data at this time.</p><p>Error <code>%1$s</code>: %2$s</p>', 'it-l10n-ithemes-sync' ), $error['error']['type'], $error['error']['message'] );
60
+ } else {
61
+ return __( '<p>Unable to get changelog data at this time.</p><p>Error: Unrecognized response from iThemes API server.</p>', 'it-l10n-ithemes-sync' );
62
+ }
63
+ }
64
+
65
+
66
+ $versions = array();
67
+ $version = false;
68
+ $depth = 0;
69
+
70
+ $lines = preg_split( '/[\n\r]+/', $body );
71
+
72
+ foreach ( $lines as $line ) {
73
+ if ( preg_match( '/^\d/', $line ) ) {
74
+ if ( ! empty( $version ) && ( $depth > 0 ) ) {
75
+ while ( $depth-- > 0 ) {
76
+ $versions[$version] .= "</ul>\n";
77
+ }
78
+ }
79
+
80
+ $depth = 0;
81
+
82
+ $parts = preg_split( '/\s+-\s+/', $line );
83
+ $version = $parts[0];
84
+
85
+ if ( version_compare( $version, $cur_version, '<=' ) ) {
86
+ $version = '';
87
+ continue;
88
+ }
89
+
90
+ $versions[$version] = '';
91
+
92
+ continue;
93
+ } else if ( preg_match( '/^\S/', $line ) ) {
94
+ $version = '';
95
+ continue;
96
+ } else if ( empty( $version ) ) {
97
+ continue;
98
+ }
99
+
100
+ $line = str_replace( ' ', "\t", $line );
101
+ $line = str_replace( "\t", '', $line, $count );
102
+ $line = preg_replace( '/^\s+/', '', $line );
103
+
104
+ if ( empty( $line ) ) {
105
+ continue;
106
+ }
107
+
108
+ $details = '';
109
+
110
+ if ( $count > $depth ) {
111
+ $details .= "<ul>\n";
112
+ $depth++;
113
+ } else if ( $count < $depth ) {
114
+ $details .= "</ul>\n";
115
+ $depth--;
116
+ }
117
+
118
+ $details .= "<li>$line</li>\n";
119
+
120
+
121
+ $versions[$version] .= $details;
122
+ }
123
+
124
+ if ( ! empty( $version ) && ( $depth > 0 ) ) {
125
+ while ( $depth-- > 0 ) {
126
+ $versions[$version] .= "</ul>\n";
127
+ }
128
+ }
129
+
130
+
131
+ uksort( $versions, 'version_compare' );
132
+ $versions = array_reverse( $versions );
133
+
134
+
135
+ $changelog = '';
136
+
137
+ foreach ( $versions as $version => $details ) {
138
+ $changelog .= "<h4>$version</h4>\n$details\n";
139
+ }
140
+
141
+ $changelog = preg_replace( '/\s+$/', '', $changelog );
142
+
143
+
144
+ return $changelog;
145
+ }
146
+
147
+ public static function get_response( $action, $args, $cache ) {
148
+ require_once( $GLOBALS['ithemes_updater_path'] . '/server.php' );
149
+ require_once( $GLOBALS['ithemes_updater_path'] . '/updates.php' );
150
+
151
+
152
+ if ( isset( $args['packages'] ) ) {
153
+ $args['packages'] = self::get_request_package_details( $args['packages'] );
154
+ }
155
+
156
+
157
+ $response = false;
158
+ $source = '';
159
+ $cached = true;
160
+
161
+ $md5 = substr( md5( serialize( $args ) ), 0, 5 );
162
+ $time = time();
163
+ $cache_var = "it-updater-$action-$md5";
164
+
165
+
166
+ if ( $cache ) {
167
+ if ( isset( $GLOBALS[$cache_var] ) ) {
168
+ $response = $GLOBALS[$cache_var];
169
+ $source = 'GLOBALS';
170
+ } else if ( false !== ( $transient = get_site_transient( $cache_var ) ) ) {
171
+ $response = $transient;
172
+ $source = 'transient';
173
+ }
174
+ }
175
+
176
+
177
+ if ( false === $response ) {
178
+ $response = call_user_func_array( array( 'Ithemes_Updater_Server', $action ), $args );
179
+ $source = 'server';
180
+
181
+ if ( is_wp_error( $response ) ) {
182
+ return $response;
183
+ }
184
+
185
+ $cache_length = 86400 * $GLOBALS['ithemes-updater-settings']->get_option( 'timeout-multiplier' );
186
+
187
+ set_site_transient( $cache_var, $response, $cache_length );
188
+
189
+ $cached = false;
190
+ }
191
+
192
+
193
+ Ithemes_Updater_Updates::process_server_response( $response, $cached );
194
+
195
+ $GLOBALS[$cache_var] = $response;
196
+
197
+ return $response;
198
+ }
199
+
200
+ private static function get_request_package_details( $desired_packages = array() ) {
201
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
202
+ require_once( $GLOBALS['ithemes_updater_path'] . '/keys.php' );
203
+
204
+
205
+ $all_packages = Ithemes_Updater_Packages::get_local_details();
206
+ reset( $desired_packages );
207
+
208
+ if ( empty( $desired_packages ) ) {
209
+ $desired_packages = $all_packages;
210
+ } else if ( is_numeric( key( $desired_packages ) ) ) {
211
+ $new_desired_packages = array();
212
+
213
+ foreach ( $all_packages as $path => $details ) {
214
+ foreach ( $desired_packages as $package ) {
215
+ if ( $package != $details['package'] ) {
216
+ continue;
217
+ }
218
+
219
+ $new_desired_packages[$path] = $details;
220
+
221
+ break;
222
+ }
223
+ }
224
+
225
+ $desired_packages = $new_desired_packages;
226
+ }
227
+
228
+
229
+ $packages = array();
230
+ $keys = Ithemes_Updater_Keys::get();
231
+
232
+
233
+ $package_slugs = array();
234
+
235
+ foreach ( $desired_packages as $data ) {
236
+ $package_slugs[] = $data['package'];
237
+ }
238
+
239
+ $legacy_keys = Ithemes_Updater_Keys::get_legacy( $package_slugs );
240
+
241
+
242
+ $active_themes = array(
243
+ 'stylesheet' => get_stylesheet_directory(),
244
+ 'template' => get_template_directory(),
245
+ );
246
+ $active_themes = array_unique( $active_themes );
247
+
248
+ foreach ( $active_themes as $index => $path ) {
249
+ $active_themes[$index] = basename( $path );
250
+ }
251
+
252
+
253
+ foreach ( $desired_packages as $path => $data ) {
254
+ $key = ( isset( $keys[$data['package']] ) ) ? $keys[$data['package']] : '';
255
+
256
+ $package = array(
257
+ 'ver' => $data['installed'],
258
+ 'key' => $key,
259
+ );
260
+
261
+ if ( ! empty( $legacy_keys[$data['package']] ) ) {
262
+ $package['old-key'] = $legacy_keys[$data['package']];
263
+ }
264
+
265
+
266
+ if ( 'plugins' == $data['type'] ) {
267
+ $package['active'] = (int) is_plugin_active( $path );
268
+ } else {
269
+ $dir = dirname( $path );
270
+
271
+ $package['active'] = (int) in_array( $dir, $active_themes );
272
+
273
+ if ( $package['active'] && ( count( $active_themes ) > 1 ) ) {
274
+ if ( $dir == $active_themes['stylesheet'] ) {
275
+ $package['child-theme'] = 1;
276
+ } else {
277
+ $package['parent-theme'] = 1;
278
+ }
279
+ }
280
+ }
281
+
282
+
283
+ $package_key = $data['package'];
284
+ $counter = 0;
285
+
286
+ while ( isset( $packages[$package_key] ) ) {
287
+ $package_key = "{$data['package']} ||| " . ++$counter;
288
+ }
289
+
290
+ $packages[$package_key] = $package;
291
+ }
292
+
293
+
294
+ if ( ! empty( $legacy_keys ) ) {
295
+ Ithemes_Updater_Keys::delete_legacy( array_keys( $legacy_keys ) );
296
+ }
297
+
298
+
299
+ return $packages;
300
+ }
301
+ }
lib/updater/api.txt ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # iThemes Updater API #
2
+
3
+ This document lists the different API features of the iThemes Updater system.
4
+
5
+ Note: This document is in [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown) format.
6
+
7
+
8
+ ## Defines ##
9
+
10
+ The entire updater system can be disabled by creating a define named ITHEMES_UPDATER_DISABLE with a truthy value. For example, the following could be added to the wp-config.php file to disable the updater system:
11
+
12
+ define( 'ITHEMES_UPDATER_DISABLE', true );
13
+
14
+
15
+ ## Query Strings ##
16
+
17
+ The updater system has built in refresh intervals:
18
+
19
+ * One minute - When on the Dashboard > Updates or Settings > iThemes Licensing pages, the data will refresh in one minute intervals. This means that once the data is a minute or older, it will be refreshed.
20
+ * One hour - When on the Plugins > Installed Plugins or Appearance > Themes pages, the data will refresh in one hour intervals.
21
+ * Twelve hours - On all other pages the data will refresh in twelve hour intervals.
22
+
23
+ Note that data is only refreshed when in the WordPress Dashboard (wp-admin). This prevents adding additional load time when people are browsing the site.
24
+
25
+ You can force a manual refresh of the data by modifying the URL of the page to include a non-empty query variable named ithemes-updater-force-refresh. This can be helpful if you think that the updater is having a problem and isn't properly refreshing itself.
26
+
27
+ For example, if you are on an admin page with a URL of http://example.com/wp-admin/plugins.php, you can force a refresh by changing the URL to the following:
28
+
29
+ http://example.com/wp-admin/plugins.php?ithemes-updater-force-refresh=1
30
+
31
+ Note that forcing a refresh only works on the WordPress Dashboard and not on the site front-end. In addition, only users with the manage_options capability have the ability to force a refresh. By default, the only users with this capability are the ones in the Administrator role. These restrictions are to help prevent a non-authorized user from consuming server resources by forcing numerous refreshes.
32
+
33
+
34
+ Some of our releases have versions with four digits, such as 3.1.1.1. Releases with a four-digit version are quick releases and are meant for rapid bug fix releases (typically in response to a specific support request). If the version on the site is only different by this last digit in a four-digit version (such as currently running 3.1.1.1 when 3.1.1.4 is available), then the updater will not show an update as available. You can force the updater to make such releases available by modifying an admin page URL to include a non-empty query variable named ithemes-updater-force-minor-update.
35
+
36
+ For example, if you are on an admin page with a URL of http://example.com/wp-admin/plugins.php, you can force quick bug fix releases to be avaiable by changing the URL to the following:
37
+
38
+ http://example.com/wp-admin/plugins.php?ithemes-updater-force-minor-update=1
39
+
40
+ Note: The updater now supports a query variable named ithemes-updater-force-quick-release-update. It has the same functionality as ithemes-updater-force-minor-update.
lib/updater/ca/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
lib/updater/ca/roots.crt ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ##
2
+ ## CA Root Certificate for api.ithemes.com and s3.amazonaws.com
3
+ ##
4
+ ## Certificate data from Mozilla as of: Sat Dec 29 20:03:40 2012
5
+ ##
6
+
7
+ # @(#) $RCSfile: certdata.txt,v $ $Revision: 1.87 $ $Date: 2012/12/29 16:32:45 $
8
+
9
+ Go Daddy Class 2 CA
10
+ ===================
11
+ -----BEGIN CERTIFICATE-----
12
+ MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
13
+ VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
14
+ ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
15
+ A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
16
+ RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
17
+ ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
18
+ 2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
19
+ qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
20
+ YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
21
+ vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
22
+ BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
23
+ atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
24
+ MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
25
+ A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
26
+ PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
27
+ I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
28
+ HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
29
+ Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
30
+ vZ8=
31
+ -----END CERTIFICATE-----
32
+
33
+ VeriSign Class 3 Public Primary Certification Authority - G5
34
+ ============================================================
35
+ -----BEGIN CERTIFICATE-----
36
+ MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE
37
+ BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
38
+ ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
39
+ IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp
40
+ ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB
41
+ yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln
42
+ biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh
43
+ dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt
44
+ YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
45
+ ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz
46
+ j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD
47
+ Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
48
+ Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r
49
+ fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/
50
+ BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
51
+ Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
52
+ aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG
53
+ SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+
54
+ X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE
55
+ KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC
56
+ Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE
57
+ ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
58
+ -----END CERTIFICATE-----
lib/updater/class-ithemes-credentials.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ //-----------------------------------------------------------------------------
4
+
5
+ class iThemes_Credentials
6
+ {
7
+
8
+ //-----------------------------------------------------------------------------
9
+
10
+ protected $hash = 'sha256';
11
+
12
+ protected $salt_padding = 'wdHVwU&HcYcWnllo%kTUUnxpScy4%ICM29';
13
+
14
+ protected $iteration_count = 131072;
15
+
16
+ protected $key_length = 64;
17
+
18
+ protected $password;
19
+
20
+ //-----------------------------------------------------------------------------
21
+
22
+ public function __construct($username, $password, $options = array())
23
+ {
24
+
25
+ $this->username = $username;
26
+
27
+ $this->password = $password;
28
+
29
+
30
+ if(!empty($options['hash']))
31
+ $this->hash = strtolower(trim($options['hash']));
32
+
33
+ if(!empty($options['iterations']))
34
+ $this->iteration_count = intval($options['iterations']);
35
+
36
+ if(!empty($options['salt']))
37
+ $this->salt_padding = $options['salt'];
38
+
39
+ if(!empty($options['key_length']))
40
+ $this->key_length = intval($options['key_length']);
41
+
42
+ }
43
+
44
+ //-----------------------------------------------------------------------------
45
+
46
+ public static function get_password_hash($username, $password, $options = array())
47
+ {
48
+
49
+ $hasher = new iThemes_Credentials($username, $password, $options);
50
+
51
+ return $hasher->get_pbkdf2();
52
+
53
+ }
54
+
55
+ //-----------------------------------------------------------------------------
56
+
57
+ public function get_salt()
58
+ {
59
+
60
+ return strtolower(trim($this->username)) . $this->salt_padding;
61
+
62
+ }
63
+
64
+ //-----------------------------------------------------------------------------
65
+
66
+ public function get_pbkdf2()
67
+ {
68
+
69
+ return $this->pbkdf2($this->hash,
70
+ $this->password,
71
+ $this->get_salt(),
72
+ $this->iteration_count,
73
+ $this->key_length / 2,
74
+ false);
75
+
76
+ }
77
+
78
+ //-----------------------------------------------------------------------------
79
+
80
+ /*
81
+ * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
82
+ * $algorithm - The hash algorithm to use. Recommended: SHA256
83
+ * $password - The password.
84
+ * $salt - A salt that is unique to the password.
85
+ * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
86
+ * $key_length - The length of the derived key in bytes.
87
+ * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
88
+ * Returns: A $key_length-byte key derived from the password and salt.
89
+ *
90
+ * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
91
+ *
92
+ * This implementation of PBKDF2 was originally created by https://defuse.ca
93
+ * With improvements by http://www.variations-of-shadow.com
94
+ */
95
+ private function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
96
+ {
97
+
98
+ $algorithm = strtolower($algorithm);
99
+
100
+ if(!in_array($algorithm, hash_algos(), true))
101
+ trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
102
+
103
+ if($count <= 0 || $key_length <= 0)
104
+ trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);
105
+
106
+
107
+ $hash_length = strlen(hash($algorithm, '', true));
108
+ $block_count = ceil($key_length / $hash_length);
109
+
110
+ $output = '';
111
+
112
+ for($i = 1; $i <= $block_count; $i++)
113
+ {
114
+
115
+ // $i encoded as 4 bytes, big endian.
116
+ $last = $salt . pack("N", $i);
117
+
118
+ // first iteration
119
+ $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
120
+
121
+ // perform the other $count - 1 iterations
122
+ for ($j = 1; $j < $count; $j++)
123
+ {
124
+ $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
125
+ }
126
+
127
+ $output .= $xorsum;
128
+
129
+ }
130
+
131
+ if($raw_output)
132
+ return substr($output, 0, $key_length);
133
+ else
134
+ return bin2hex(substr($output, 0, $key_length));
135
+
136
+ }
137
+
138
+ //-----------------------------------------------------------------------------
139
+
140
+ }
141
+
142
+ //-----------------------------------------------------------------------------
143
+
lib/updater/css/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
lib/updater/css/settings-page.css ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #icon-options-general,
2
+ #icon-settings {
3
+ background: url( '../images/logo-ithemes.png' ) no-repeat;
4
+ }
5
+ #ithemes-updater-licensed .subtitle {
6
+ background: url( '../images/icon-licensed.png' ) no-repeat;
7
+ }
8
+ #ithemes-updater-unlicensed .subtitle {
9
+ background: url( '../images/icon-unlicensed.png' ) no-repeat;
10
+ }
11
+ #ithemes-updater-unrecognized .subtitle {
12
+ background: url( '../images/icon-unrecognized.png' ) no-repeat;
13
+ }
14
+ #ithemes-updater-settings .subtitle {
15
+ background: url( '../images/icon-settings.png' ) no-repeat;
16
+ }
17
+
18
+
19
+ .wrap h2 + .error,
20
+ .wrap h2 + .updated {
21
+ margin-top: 2em;
22
+ }
23
+ .wrap .subtitle {
24
+ font-size: 23px;
25
+ line-height: 30px;
26
+ margin: 1.5em 0 .5em;
27
+ padding: 0 0 0 35px;
28
+ }
29
+
30
+
31
+ .wrap .widefat.ithemes-updater-listing th,
32
+ .wrap .widefat.ithemes-updater-listing td {
33
+ padding: 0;
34
+ vertical-align: middle;
35
+ }
36
+ .wrap .widefat.ithemes-updater-listing tfoot td {
37
+ padding: 13px 10px;
38
+ }
39
+ .ithemes-updater-listing label {
40
+ display: block;
41
+ padding: 10px;
42
+ }
43
+ .widefat.ithemes-updater-listing th.check-column {
44
+ }
45
+ .widefat.ithemes-updater-listing th.check-column input {
46
+ margin: 0;
47
+ }
48
+
49
+ .wrap #ithemes-updater-unrecognized .widefat.ithemes-updater-listing th,
50
+ .wrap #ithemes-updater-unrecognized .widefat.ithemes-updater-listing td {
51
+ padding: 10px;
52
+ }
53
+
54
+
55
+ .widefat.ithemes-updater-listing tr.expiring {
56
+ background-color: #FFFFE0;
57
+ }
58
+ .widefat.ithemes-updater-listing tr.expiring.alt {
59
+ background-color: #F5F5D2;
60
+ }
61
+
62
+ .widefat.ithemes-updater-listing tr.expired {
63
+ background-color: #FFEBE8;
64
+ }
65
+ .widefat.ithemes-updater-listing tr.expired.alt {
66
+ background-color: #F9E1DD;
67
+ }
lib/updater/functions.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Misc functions to assist the updater code.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ */
12
+
13
+
14
+ class Ithemes_Updater_Functions {
15
+ public static function get_url( $path ) {
16
+ $path = str_replace( '\\', '/', $path );
17
+ $wp_content_dir = str_replace( '\\', '/', WP_CONTENT_DIR );
18
+
19
+ if ( 0 === strpos( $path, $wp_content_dir ) )
20
+ return content_url( str_replace( $wp_content_dir, '', $path ) );
21
+
22
+ $abspath = str_replace( '\\', '/', ABSPATH );
23
+
24
+ if ( 0 === strpos( $path, $abspath ) )
25
+ return site_url( str_replace( $abspath, '', $path ) );
26
+
27
+ $wp_plugin_dir = str_replace( '\\', '/', WP_PLUGIN_DIR );
28
+ $wpmu_plugin_dir = str_replace( '\\', '/', WPMU_PLUGIN_DIR );
29
+
30
+ if ( 0 === strpos( $path, $wp_plugin_dir ) || 0 === strpos( $path, $wpmu_plugin_dir ) )
31
+ return plugins_url( basename( $path ), $path );
32
+
33
+ return false;
34
+ }
35
+
36
+ public static function get_package_name( $package ) {
37
+ $name = str_replace( 'builderchild', 'Builder Child', $package );
38
+ $name = str_replace( '-', ' ', $name );
39
+ $name = ucwords( $name );
40
+ $name = str_replace( 'buddy', 'Buddy', $name );
41
+ $name = str_replace( 'Ithemes', 'iThemes', $name );
42
+
43
+ return $name;
44
+ }
45
+
46
+ public static function get_post_data( $vars, $fill_missing = false ) {
47
+ $data = array();
48
+
49
+ foreach ( $vars as $var ) {
50
+ if ( isset( $_POST[$var] ) ) {
51
+ $clean_var = preg_replace( '/^it-updater-/', '', $var );
52
+ $data[$clean_var] = $_POST[$var];
53
+ }
54
+ else if ( $fill_missing ) {
55
+ $data[$var] = '';
56
+ }
57
+ }
58
+
59
+ return stripslashes_deep( $data );
60
+ }
61
+ }
lib/updater/history.txt ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1.0.0 - 2013-04-11 - Chris Jean
2
+ Release ready version.
3
+ 1.0.1 - 2013-05-01 - Chris Jean
4
+ Fixed a compatibility problem with the automatic-updater plugin.
5
+ 1.0.2 - 2013-06-21 - Chris Jean
6
+ Fixed an issue where requests would fail when made by servers with a non-default value for arg_separator.output in the php.ini file.
7
+ 1.1.0 - 2013-10-02 - Chris Jean
8
+ Big rewrite to the updater in order to make the code more flexible. This improves compatibility with plugins that allow for remote updating of plugins and themes.
9
+ The updater now supports providing changelog details for plugins and themes that have updates available. These details can be viewed by clicking the "View version **** details" link for the appropriate plugin or theme.
10
+ 1.2.0 - 2013-10-04 - Chris Jean
11
+ Enhancement: When releases with four version digits are released (such as 2.1.0.3), by default, the updater will not offer to update if just this last digit is updated. For instance, it will not automatically offer to update 2.1.0.1 to 2.1.0.3; rather, an automatic upgrade will become available when version 2.1.1 or greater is released. If you wish to force an update to these versions, you can add ithemes-updater-force-minor-update=1 to an admin page request (such as http://domain.com/wp-admin/index.php?ithemes-updater-force-minor-update=1) in order to force the updater make these releases available for an hour. If the update is not performed within the hour, the updater reverts back to default behavior.
12
+ Bug Fix: Corrected source of the following error when updating: Download failed. SSL certificate problem, verify that the CA cert is OK. Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
13
+ Bug Fix: Corrected source of the following warning: Warning: require_once(/api.php) [function.require-once]: failed to open stream: No such file or directory
14
+ 1.2.1 - 2013-10-07 - Chris Jean
15
+ Bug Fix: Corrected another source of the following warning which occur immediate after updating the plugin or theme: Warning: require_once(/api.php) [function.require-once]: failed to open stream: No such file or directory
16
+ 1.3.0 - 2013-10-23 - Chris Jean
17
+ Enhancement: Added a "Quick Release Updates" setting to the bottom of the licensing page. Enabling this option allows the site to receive automatic update notices for product quick releases (releases that do not have automatic update support by default).
18
+ Enhancement: Changed how the licensing page works on Multisite. Now it will appear at Settings > iThemes Licensing in the network admin Dashboard and at Settings > iThemes Licensing on sites when the current user is a network admin. This prevents exposing the licensing page to users that don't are not super users and thus should not be performing updates. As this update makes the functionality of the "Licensing Page Access" setting obsolete, the setting has been removed.
19
+ Enhancement: Using the ithemes-updater-force-minor-update query string variable will now force a data flush, allowing any available quick release updates to appear instantly.
20
+ Enhancement: Added "License" links to the Plugins and Themes pages for the Network Admin Dashboard of multisite installs.
21
+ 1.3.1 - 2013-12-13 - Chris Jean
22
+ Enhancement: Recently updated plugins and themes no longer report that an update is available.
23
+ Enhancement: Added alternate line styling to the listings on the licensing page.
24
+ Enhancement: Products with iThemes in the name will now properly capitalize the name.
25
+ Enhancement: The updater and licensing system now handle iThemes server outages without causing long page delays or by reporting confusing error messages.
26
+ 1.3.2 - 2013-12-18 - Chris Jean
27
+ Bug Fix: Removed unneeded check on package-info.ithemes.com which was causing performance issues on some sites.
28
+ 1.3.3 - 2013-12-19 - Chris Jean
29
+ Bug Fix: Licensing will now be universal across all sites of a multisite network. This means that you will only need to license on one site to have the license apply across the entire network.
30
+ 1.3.4 - 2014-02-03 - Chris Jean
31
+ Bug Fix: Licensing on SSL-enabled sites now works properly.
32
+ 1.4.0 - 2014-11-14 - Chris Jean
33
+ Enhancement: Reduced caching to allow updates to appear more quickly.
34
+ Enhancement: Authenticating users now uses the new authentication system.
35
+ 1.4.1 - 2015-04-23 - Chris Jean
36
+ Compatibility Fix: Updated plugin and theme update data format to match changes in WordPress 4.2.
37
+ 1.4.2 - 2015-07-14 - Chris Jean
38
+ Enhancement: Updated link to iThemes support.
lib/updater/images/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
lib/updater/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
lib/updater/information.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Provides details formatted for use in "View version *** details" boxes.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.1
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ 1.0.1 - 2013-09-19 - Chris Jean
12
+ Updated requires to not use dirname().
13
+ 1.1.0 - 2013-10-02 - Chris Jean
14
+ Added get_theme_information().
15
+ 1.1.1 - 2013-12-18 - Chris Jean
16
+ Removed unneeded code that checked package-info.ithemes.com.
17
+ */
18
+
19
+
20
+ class Ithemes_Updater_Information {
21
+ public static function get_theme_information( $path ) {
22
+ return self::get_plugin_information( "$path/style.css" );
23
+ }
24
+
25
+ public static function get_plugin_information( $path ) {
26
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
27
+ $details = Ithemes_Updater_Packages::get_full_details();
28
+
29
+ if ( ! isset( $details['packages'][$path] ) )
30
+ return false;
31
+
32
+
33
+ $package = $details['packages'][$path];
34
+
35
+ require_once( $GLOBALS['ithemes_updater_path'] . '/functions.php' );
36
+ require_once( $GLOBALS['ithemes_updater_path'] . '/information.php' );
37
+
38
+ $changelog = Ithemes_Updater_API::get_package_changelog( $package['package'], $details['packages'][$path]['installed'] );
39
+
40
+
41
+ $info = array(
42
+ 'name' => Ithemes_Updater_Functions::get_package_name( $package['package'] ),
43
+ 'slug' => dirname( $path ),
44
+ 'version' => $package['available'],
45
+ 'author' => '<a href="http://ithemes.com/">iThemes</a>',
46
+ 'download_link' => $package['package-url'],
47
+
48
+ 'sections' => array(
49
+ 'changelog' => $changelog,
50
+ ),
51
+ );
52
+
53
+
54
+ return (object) $info;
55
+ }
56
+ }
lib/updater/init.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Load the updater and licensing system without loading unneeded parts.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.1
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ 1.0.1 - 2013-05-01 - Chris Jean
12
+ Fixed a bug where some plugins caused the filter_update_plugins and filter_update_themes to run when load hadn't run, causing errors.
13
+ 1.1.0 - 2013-09-19 - Chris Jean
14
+ Complete restructuring of this file as most of the code has been relocated to other files.
15
+ 1.2.0 - 2013-12-13 - Chris Jean
16
+ Added the ability to force clear the server timeout hold by adding a query variable named ithemes-updater-force-clear-server-timeout-hold to the URL.
17
+ 1.2.1 - 2014-10-23 - Chris Jean
18
+ Removed ithemes-updater-force-clear-server-timeout-hold code.
19
+ */
20
+
21
+
22
+ if ( defined( 'ITHEMES_UPDATER_DISABLE' ) && ITHEMES_UPDATER_DISABLE ) {
23
+ return;
24
+ }
25
+
26
+
27
+ $GLOBALS['ithemes_updater_path'] = dirname( __FILE__ );
28
+
29
+
30
+ if ( is_admin() ) {
31
+ require( $GLOBALS['ithemes_updater_path'] . '/admin.php' );
32
+ }
33
+
34
+
35
+ function ithemes_updater_filter_update_plugins( $update_plugins ) {
36
+ if ( ! class_exists( 'Ithemes_Updater_Settings' ) ) {
37
+ require( $GLOBALS['ithemes_updater_path'] . '/settings.php' );
38
+ }
39
+
40
+ return $GLOBALS['ithemes-updater-settings']->filter_update_plugins( $update_plugins );
41
+ }
42
+ add_filter( 'site_transient_update_plugins', 'ithemes_updater_filter_update_plugins' );
43
+ add_filter( 'transient_update_plugins', 'ithemes_updater_filter_update_plugins' );
44
+
45
+
46
+ function ithemes_updater_filter_update_themes( $update_themes ) {
47
+ if ( ! class_exists( 'Ithemes_Updater_Settings' ) ) {
48
+ require( $GLOBALS['ithemes_updater_path'] . '/settings.php' );
49
+ }
50
+
51
+ return $GLOBALS['ithemes-updater-settings']->filter_update_themes( $update_themes );
52
+ }
53
+ add_filter( 'site_transient_update_themes', 'ithemes_updater_filter_update_themes' );
54
+ add_filter( 'transient_update_themes', 'ithemes_updater_filter_update_themes' );
lib/updater/keys.php ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Provides license key management.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.2
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ 1.0.1 - 2013-09-19 - Chris Jean
12
+ Updated requires to no longer use dirname().
13
+ 1.0.2 - 2014-10-23 - Chris Jean
14
+ Updated code to meet WordPress coding standards.
15
+ */
16
+
17
+
18
+ class Ithemes_Updater_Keys {
19
+ private static $option_name = 'ithemes-updater-keys';
20
+
21
+
22
+ public static function get( $packages = array() ) {
23
+ $all_keys = get_site_option( self::$option_name, array() );
24
+
25
+ if ( '__all__' == $packages ) {
26
+ return $all_keys;
27
+ }
28
+
29
+ if ( empty( $packages ) ) {
30
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
31
+ $packages = array_unique( array_values( Ithemes_Updater_Packages::get_all() ) );
32
+ }
33
+
34
+
35
+ $keys = array();
36
+
37
+ foreach ( (array) $packages as $package ) {
38
+ if ( ! empty( $all_keys[$package] ) ) {
39
+ $keys[$package] = $all_keys[$package];
40
+ }
41
+ }
42
+
43
+
44
+ if ( ! is_array( $packages ) ) {
45
+ return $keys[$packages];
46
+ }
47
+
48
+ return $keys;
49
+ }
50
+
51
+ public static function set( $new_keys, $key = false ) {
52
+ $keys = self::get( '__all__' );
53
+
54
+ if ( false === $key ) {
55
+ foreach ( $new_keys as $package => $key ) {
56
+ $keys[$package] = $key;
57
+ }
58
+ } else {
59
+ $keys[$new_keys] = $key;
60
+ }
61
+
62
+ update_site_option( self::$option_name, $keys );
63
+ }
64
+
65
+ private static function get_legacy_slug( $raw_slug ) {
66
+ $slug = str_replace( '_', '-', $raw_slug );
67
+ $slug = preg_replace( '/^(pluginbuddy|ithemes|it)-/', '', $slug );
68
+
69
+ if ( 'boom-bar' == $slug ) {
70
+ $slug = 'boombar';
71
+ }
72
+
73
+ return $slug;
74
+ }
75
+
76
+ public static function delete_legacy( $packages = array() ) {
77
+ if ( ! is_array( $packages ) ) {
78
+ $packages = array( $packages );
79
+ }
80
+
81
+ $data = get_site_option( 'pluginbuddy_plugins', false );
82
+
83
+
84
+ $remaining_count = 0;
85
+
86
+ foreach ( $data as $index => $entry ) {
87
+ if ( ! is_object( $entry ) || empty( $entry->slug ) ) {
88
+ continue;
89
+ }
90
+
91
+ $slug = self::get_legacy_slug( $entry->slug );
92
+
93
+ if ( in_array( $slug, $packages ) ) {
94
+ unset( $data[$index] );
95
+ } else {
96
+ $remaining_count++;
97
+ }
98
+ }
99
+
100
+ if ( 0 == $remaining_count ) {
101
+ $data = false;
102
+ }
103
+
104
+
105
+ update_site_option( 'pluginbuddy_plugins', $data );
106
+ }
107
+
108
+ public static function get_legacy( $packages = array() ) {
109
+ $data = get_site_option( 'pluginbuddy_plugins', false );
110
+
111
+ if ( empty( $data ) || ! is_array( $data ) ) {
112
+ return array();
113
+ }
114
+
115
+
116
+ $keys = array();
117
+
118
+ foreach ( $data as $index => $entry ) {
119
+ if ( ! is_object( $entry ) || empty( $entry->slug ) || ! isset( $entry->key ) ) {
120
+ continue;
121
+ }
122
+
123
+ $slug = self::get_legacy_slug( $entry->slug );
124
+ $keys[$slug] = $entry->key;
125
+ }
126
+
127
+
128
+ foreach ( array_keys( $keys ) as $slug ) {
129
+ if ( ! isset( $data[$slug] ) ) {
130
+ continue;
131
+ }
132
+
133
+ $entry = $data[$slug];
134
+
135
+ if ( ! is_object( $entry ) || empty( $entry->slug ) || empty( $entry->key ) ) {
136
+ continue;
137
+ }
138
+
139
+ $keys[$slug] = $entry->key;
140
+ }
141
+
142
+
143
+ if ( empty( $packages ) ) {
144
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
145
+ $packages = array_unique( array_values( Ithemes_Updater_Packages::get_all() ) );
146
+ } else if ( is_string( $packages ) ) {
147
+ if ( ! empty( $keys[$packages] ) ) {
148
+ return $keys[$packages];
149
+ }
150
+
151
+ return false;
152
+ }
153
+
154
+
155
+ $package_keys = array();
156
+
157
+ foreach ( $packages as $package ) {
158
+ if ( ! empty( $keys[$package] ) ) {
159
+ $package_keys[$package] = $keys[$package];
160
+ }
161
+ }
162
+
163
+ return $package_keys;
164
+ }
165
+ }
lib/updater/load.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Written by Chris Jean for iThemes.com
5
+ */
6
+
7
+
8
+ $it_registration_list_version = '1.4.2';
9
+ $it_registration_list_library = 'updater';
10
+ $it_registration_list_init_file = dirname( __FILE__ ) . '/init.php';
11
+
12
+ $GLOBALS['it_classes_registration_list'][$it_registration_list_library][$it_registration_list_version] = $it_registration_list_init_file;
13
+
14
+ if ( ! function_exists( 'it_registration_list_init' ) ) {
15
+ function it_registration_list_init() {
16
+ // The $wp_locale variable is set just before the theme's functions.php file is loaded,
17
+ // this acts as a good check to ensure that both the plugins and the theme have loaded.
18
+ global $wp_locale;
19
+ if ( ! isset( $wp_locale ) )
20
+ return;
21
+
22
+
23
+ $init_files = array();
24
+
25
+ foreach ( (array) $GLOBALS['it_classes_registration_list'] as $library => $versions ) {
26
+ $max_version = '-10000';
27
+ $init_file = '';
28
+
29
+ foreach ( (array) $versions as $version => $file ) {
30
+ if ( version_compare( $version, $max_version, '>' ) ) {
31
+ $max_version = $version;
32
+ $init_file = $file;
33
+ }
34
+ }
35
+
36
+ if ( ! empty( $init_file ) )
37
+ $init_files[] = $init_file;
38
+ }
39
+
40
+ unset( $GLOBALS['it_classes_registration_list'] );
41
+
42
+ foreach ( (array) $init_files as $init_file )
43
+ require_once( $init_file );
44
+
45
+ do_action( 'it_libraries_loaded' );
46
+ }
47
+
48
+ global $wp_version;
49
+
50
+ if ( version_compare( $wp_version, '2.9.7', '>' ) )
51
+ add_action( 'after_setup_theme', 'it_registration_list_init' );
52
+ else
53
+ add_action( 'set_current_user', 'it_registration_list_init' );
54
+ }
lib/updater/packages.php ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Provides a reliable way of retrieving which projects have updates.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.2
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ 1.0.1 - 2013-09-19 - Chris Jean
12
+ Added support for 'upgrade' data for a package.
13
+ Updated requires to no longer use dirname().
14
+ 1.0.2 - 2014-10-23 - Chris Jean
15
+ Updated to meet WordPress coding standards.
16
+ */
17
+
18
+
19
+ class Ithemes_Updater_Packages {
20
+ public static function flush() {
21
+ unset( $GLOBALS['ithemes-updater-packages-all'] );
22
+ }
23
+
24
+ public static function get_full_details( $response = false ) {
25
+ if ( false === $response ) {
26
+ require_once( $GLOBALS['ithemes_updater_path'] . '/api.php' );
27
+ $response = Ithemes_Updater_API::get_package_details();
28
+ }
29
+
30
+ $packages = self::get_local_details();
31
+
32
+
33
+ if ( is_wp_error( $response ) ) {
34
+ $expiration = time() + 600;
35
+
36
+ foreach ( $packages as $path => $data ) {
37
+ $packages[$path]['status'] = 'error';
38
+
39
+ $packages[$path]['error'] = array(
40
+ 'code' => 'unknown',
41
+ 'type' => $response->get_error_code(),
42
+ 'message' => $response->get_error_message(),
43
+ );
44
+ }
45
+ } else {
46
+ $expiration = time() + ( 12 * 3600 );
47
+
48
+ foreach ( $packages as $path => $data ) {
49
+ if ( empty( $response['packages'][$data['package']] ) ) {
50
+ continue;
51
+ }
52
+
53
+ $package = $response['packages'][$data['package']];
54
+
55
+ if ( ! empty( $package['error'] ) ) {
56
+ if ( in_array( $package['error']['type'], array( 'ITXAPI_License_Key_Unknown', 'ITXAPI_Updater_Missing_Legacy_Key' ) ) ) {
57
+ $packages[$path]['status'] = 'unlicensed';
58
+ } else {
59
+ $packages[$path]['status'] = 'error';
60
+ $packages[$path]['error'] = $package['error'];
61
+ }
62
+
63
+ continue;
64
+ }
65
+
66
+
67
+ $key_map = array(
68
+ 'status' => 'status',
69
+ 'link' => 'package-url',
70
+ 'ver' => 'available',
71
+ 'used' => 'used',
72
+ 'total' => 'total',
73
+ 'user' => 'user',
74
+ 'sub_expire' => 'expiration',
75
+ 'upgrade' => 'upgrade',
76
+ );
77
+
78
+ foreach ( $key_map as $old => $new ) {
79
+ if ( isset( $package[$old] ) ) {
80
+ $packages[$path][$new] = $package[$old];
81
+ } else {
82
+ $packages[$path][$new] = null;
83
+ }
84
+ }
85
+
86
+ if ( isset( $package['link_expire'] ) ) {
87
+ $expiration = min( $expiration, $package['link_expire'] );
88
+ }
89
+ }
90
+ }
91
+
92
+
93
+ $details = compact( 'packages', 'expiration' );
94
+
95
+ return $details;
96
+ }
97
+
98
+ public static function get_local_details() {
99
+ require_once( $GLOBALS['ithemes_updater_path'] . '/keys.php' );
100
+
101
+
102
+ $all_packages = self::get_all();
103
+ $keys = Ithemes_Updater_Keys::get();
104
+
105
+ $packages = array();
106
+
107
+ foreach ( $all_packages as $file => $slug ) {
108
+ $packages[$slug][] = $file;
109
+ }
110
+
111
+ foreach ( $packages as $slug => $paths ) {
112
+ $packages[$slug] = array_unique( $paths );
113
+ }
114
+
115
+
116
+ $details = array();
117
+
118
+
119
+ foreach ( $packages as $package => $paths ) {
120
+ foreach ( $paths as $path ) {
121
+ $plugin_path = preg_replace( '/^' . preg_quote( WP_PLUGIN_DIR, '/' ) . '/', '', $path );
122
+
123
+ if ( $plugin_path != $path ) {
124
+ $type = 'plugin';
125
+ $rel_path = preg_replace( '|^[/\\\\]|', '', $plugin_path );
126
+
127
+ $plugin_data = get_plugin_data( $path, false, false );
128
+ $version = $plugin_data['Version'];
129
+ $info_url = $plugin_data['PluginURI'];
130
+ } else {
131
+ $type = 'theme';
132
+ $dir = basename( dirname( $path ) );
133
+ $rel_path = "$dir/" . basename( $path );
134
+
135
+ if ( function_exists( 'wp_get_theme' ) ) {
136
+ $theme_data = wp_get_theme( $dir );
137
+
138
+ $version = $theme_data->get( 'Version' );
139
+ $info_url = $theme_data->get( 'ThemeURI' );
140
+ } else {
141
+ $theme_data = get_theme( $dir );
142
+
143
+ $version = $theme_data['Version'];
144
+ $info_url = '';
145
+ }
146
+ }
147
+
148
+
149
+ $details[$rel_path] = array(
150
+ 'type' => $type,
151
+ 'package' => $package,
152
+ 'installed' => $version,
153
+ 'info-url' => $info_url,
154
+ 'key' => isset( $keys[$package] ) ? $keys[$package] : '',
155
+ );
156
+ }
157
+ }
158
+
159
+
160
+ return $details;
161
+ }
162
+
163
+ public static function get_all() {
164
+ if ( isset( $GLOBALS['ithemes-updater-packages-all'] ) ) {
165
+ return $GLOBALS['ithemes-updater-packages-all'];
166
+ }
167
+
168
+
169
+ $packages = array();
170
+
171
+
172
+ // Compatibility fix for WP < 3.1 as the global var is empty by default
173
+ if ( empty( $GLOBALS['wp_theme_directories'] ) ) {
174
+ get_themes();
175
+ }
176
+
177
+ $themes = search_theme_directories();
178
+
179
+ if ( is_array( $themes ) ) {
180
+ foreach ( $themes as $slug => $data ) {
181
+ if ( ! file_exists( "{$data['theme_root']}/{$data['theme_file']}" ) ) {
182
+ continue;
183
+ }
184
+
185
+ $headers = get_file_data( "{$data['theme_root']}/{$data['theme_file']}", array( 'package' => 'iThemes Package' ), 'theme' );
186
+
187
+ if ( empty( $headers['package'] ) ) {
188
+ continue;
189
+ }
190
+
191
+ $packages["{$data['theme_root']}/{$data['theme_file']}"] = $headers['package'];
192
+ }
193
+ }
194
+
195
+
196
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
197
+ $plugins = get_plugins();
198
+
199
+ foreach ( $plugins as $file => $data ) {
200
+ if ( ! file_exists( WP_PLUGIN_DIR . "/$file" ) ) {
201
+ continue;
202
+ }
203
+
204
+ $headers = get_file_data( WP_PLUGIN_DIR . "/$file", array( 'package' => 'iThemes Package' ), 'plugin' );
205
+
206
+ if ( empty( $headers['package'] ) ) {
207
+ continue;
208
+ }
209
+
210
+ $packages[WP_PLUGIN_DIR . "/$file"] = $headers['package'];
211
+ }
212
+
213
+
214
+ foreach ( $packages as $path => $package ) {
215
+ $packages[$path] = strtolower( $package );
216
+ }
217
+
218
+
219
+ $GLOBALS['ithemes-updater-packages-all'] = $packages;
220
+
221
+ return $packages;
222
+ }
223
+ }
lib/updater/server.php ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Provides an easy to use interface for communicating with the iThemes updater server.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ 1.0.1 - 2013-06-21 - Chris Jean
12
+ Updated the http_build_query call to force a separator of & in order to avoid issues with servers that change the arg_separator.output php.ini value.
13
+ 1.0.2 - 2013-09-19 - Chris Jean
14
+ Updated ithemes-updater-object to ithemes-updater-settings.
15
+ 1.0.3 - 2013-12-18 - Chris Jean
16
+ Updated the way that the site URL is generated to ensure consistency across multisite sites.
17
+ 1.0.4 - 2014-01-31 - Chris Jean
18
+ Updated to normalize the site URL used for password hash generation and for sending to the server.
19
+ 1.1.0 - 2014-10-23 - Chris Jean
20
+ Updated auth token generation to use new password hashing.
21
+ Added CA patch code.
22
+ Updated code to meet WordPress coding standards.
23
+ */
24
+
25
+
26
+ class Ithemes_Updater_Server {
27
+ private static $secure_server_url = 'https://api.ithemes.com/updater';
28
+ private static $insecure_server_url = 'http://api.ithemes.com/updater';
29
+
30
+ private static $password_iterations = 8;
31
+
32
+
33
+ public static function activate_package( $username, $password, $packages ) {
34
+ $query = array(
35
+ 'user' => $username
36
+ );
37
+
38
+ $data = array(
39
+ 'auth_token' => self::get_password_hash( $username, $password ),
40
+ 'packages' => $packages,
41
+ );
42
+
43
+ return Ithemes_Updater_Server::request( 'package-activate', $query, $data );
44
+ }
45
+
46
+ public static function deactivate_package( $username, $password, $packages ) {
47
+ $query = array(
48
+ 'user' => $username
49
+ );
50
+
51
+ $data = array(
52
+ 'auth_token' => self::get_password_hash( $username, $password ),
53
+ 'packages' => $packages,
54
+ );
55
+
56
+ return Ithemes_Updater_Server::request( 'package-deactivate', $query, $data );
57
+ }
58
+
59
+ public static function get_package_details( $packages ) {
60
+ $query = array();
61
+
62
+ $data = array(
63
+ 'packages' => $packages
64
+ );
65
+
66
+ return Ithemes_Updater_Server::request( 'package-details', $query, $data );
67
+ }
68
+
69
+ public static function request( $action, $query = array(), $data = array() ) {
70
+ if ( false !== ( $timeout_start = get_site_option( 'ithemes-updater-server-timed-out' ) ) ) {
71
+ // Hold off updates for 30 minutes.
72
+ $time_remaining = 1800 - ( time() - $timeout_start );
73
+ $minutes_remaining = ceil( $time_remaining / 60 );
74
+
75
+ if ( $time_remaining < 0 ) {
76
+ delete_site_option( 'ithemes-updater-server-timed-out' );
77
+ } else {
78
+ return new WP_Error( 'ithemes-updater-timed-out-server', sprintf( _n( 'The server could not be contacted. Requests are delayed for %d minute to allow the server to recover.', 'The server could not be contacted. Requests are delayed for %d minutes to allow the server to recover.', $minutes_remaining, 'it-l10n-ithemes-sync' ), $minutes_remaining ) );
79
+ }
80
+ }
81
+
82
+
83
+ if ( isset( $data['auth_token'] ) ) {
84
+ $data['iterations'] = self::$password_iterations;
85
+ }
86
+
87
+
88
+ $site_url = self::get_site_url();
89
+
90
+
91
+ $default_query = array(
92
+ 'wp' => $GLOBALS['wp_version'],
93
+ 'site' => $site_url,
94
+ 'timestamp' => time(),
95
+ 'auth_version' => '2',
96
+ );
97
+
98
+ if ( is_multisite() ) {
99
+ $default_query['ms'] = 1;
100
+ }
101
+
102
+ $query = array_merge( $default_query, $query );
103
+ $request = "/$action/?" . http_build_query( $query, '', '&' );
104
+
105
+ $post_data = array(
106
+ 'request' => json_encode( $data ),
107
+ );
108
+
109
+ $remote_post_args = array(
110
+ 'timeout' => 10,
111
+ 'body' => $post_data,
112
+ );
113
+
114
+
115
+ $options = array(
116
+ 'use_ca_patch' => false,
117
+ 'use_ssl' => true,
118
+ );
119
+
120
+ $patch_enabled = $GLOBALS['ithemes-updater-settings']->get_option( 'use_ca_patch' );
121
+
122
+ if ( $patch_enabled ) {
123
+ self::disable_ssl_ca_patch();
124
+ }
125
+
126
+
127
+ $response = wp_remote_post( self::$secure_server_url . $request, $remote_post_args );
128
+
129
+ if ( is_wp_error( $response ) && ( 'connect() timed out!' != $response->get_error_message() ) ) {
130
+ self::enable_ssl_ca_patch();
131
+ $response = wp_remote_post( self::$secure_server_url . $request, $remote_post_args );
132
+
133
+ if ( ! is_wp_error( $response ) ) {
134
+ $options['use_ca_patch'] = true;
135
+ }
136
+ }
137
+
138
+ if ( is_wp_error( $response ) && ( 'connect() timed out!' != $response->get_error_message() ) ) {
139
+ $response = wp_remote_post( self::$insecure_server_url . $request, $remote_post_args );
140
+
141
+ $options['use_ssl'] = false;
142
+ }
143
+
144
+ if ( ! $options['use_ca_patch'] ) {
145
+ self::disable_ssl_ca_patch();
146
+ }
147
+
148
+ $GLOBALS['ithemes-updater-settings']->update_options( $options );
149
+
150
+ if ( is_wp_error( $response ) ) {
151
+ if ( 'connect() timed out!' == $response->get_error_message() ) {
152
+ // Set option to delay server checks for a period of time.
153
+ update_site_option( 'ithemes-updater-server-timed-out', time() );
154
+
155
+ return new WP_Error( 'http_request_failed', __( 'The server was unable to be contacted.', 'it-l10n-ithemes-sync' ) );
156
+ }
157
+
158
+ return $response;
159
+ }
160
+
161
+
162
+ $body = json_decode( $response['body'], true );
163
+
164
+ if ( ! empty( $body['error'] ) ) {
165
+ return new WP_Error( $body['error']['type'], sprintf( __( 'An error occurred when communicating with the iThemes update server: %s (%s)', 'it-l10n-ithemes-sync' ), $body['error']['message'], $body['error']['code'] ) );
166
+ }
167
+
168
+
169
+ return $body;
170
+ }
171
+
172
+ private static function get_site_url() {
173
+ if ( is_callable( 'network_home_url' ) ) {
174
+ $site_url = network_home_url();
175
+ } else {
176
+ $site_url = get_bloginfo( 'url' );
177
+ }
178
+
179
+ $site_url = preg_replace( '/^https/', 'http', $site_url );
180
+ $site_url = preg_replace( '|/$|', '', $site_url );
181
+
182
+ return $site_url;
183
+ }
184
+
185
+ private static function get_password_hash( $username, $password ) {
186
+ require_once( ABSPATH . 'wp-includes/class-phpass.php' );
187
+ require_once( dirname( __FILE__ ) . '/class-ithemes-credentials.php' );
188
+
189
+ $password = iThemes_Credentials::get_password_hash( $username, $password );
190
+
191
+ $salted_password = $password . $username . self::get_site_url() . $GLOBALS['wp_version'];
192
+ $salted_password = substr( $salted_password, 0, max( strlen( $password ), 512 ) );
193
+
194
+ $hasher = new PasswordHash( self::$password_iterations, true );
195
+ $auth_token = $hasher->HashPassword( $salted_password );
196
+
197
+ return $auth_token;
198
+ }
199
+
200
+ public static function enable_ssl_ca_patch() {
201
+ add_action( 'http_api_curl', array( __CLASS__, 'add_ca_patch_to_curl_opts' ) );
202
+ }
203
+
204
+ public static function disable_ssl_ca_patch() {
205
+ remove_action( 'http_api_curl', array( __CLASS__, 'add_ca_patch_to_curl_opts' ) );
206
+ }
207
+
208
+ public static function add_ca_patch_to_curl_opts( $handle ) {
209
+ $url = curl_getinfo( $handle, CURLINFO_EFFECTIVE_URL );
210
+
211
+ if ( ! preg_match( '{^https://(api|downloads)\.ithemes\.com}', $url ) ) {
212
+ return;
213
+ }
214
+
215
+ curl_setopt( $handle, CURLOPT_CAINFO, $GLOBALS['ithemes_updater_path'] . '/ca/roots.crt' );
216
+ }
217
+ }
lib/updater/settings-page.php ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Code to render and manage the settings page for the updater system.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ 1.0.1 - 2013-09-19 - Chris Jean
12
+ Updated requires to not use dirname().
13
+ Updated ithemes-updater-object to ithemes-updater-settings.
14
+ 1.1.0 - 2013-10-23 - Chris Jean
15
+ Enhancement: Added the quick_releases setting.
16
+ Misc: Removed the show_on_sites setting as it is no longer used.
17
+ */
18
+
19
+
20
+ class Ithemes_Updater_Settings_Page {
21
+ private $page_name = 'ithemes-licensing';
22
+
23
+ private $path_url = '';
24
+ private $self_url = '';
25
+ private $messages = array();
26
+ private $message_strings = array();
27
+ private $errors = array();
28
+ private $soft_errors = array();
29
+
30
+
31
+ public function __construct() {
32
+ require_once( $GLOBALS['ithemes_updater_path'] . '/functions.php' );
33
+ require_once( $GLOBALS['ithemes_updater_path'] . '/api.php' );
34
+ require_once( $GLOBALS['ithemes_updater_path'] . '/keys.php' );
35
+
36
+
37
+ $this->path_url = Ithemes_Updater_Functions::get_url( $GLOBALS['ithemes_updater_path'] );
38
+
39
+ list( $this->self_url ) = explode( '?', $_SERVER['REQUEST_URI'] );
40
+ $this->self_url .= '?page=' . $this->page_name;
41
+
42
+
43
+ add_action( 'ithemes_updater_settings_page_index', array( $this, 'index' ) );
44
+ add_action( 'admin_print_styles', array( $this, 'add_styles' ) );
45
+ }
46
+
47
+ public function add_styles() {
48
+ wp_enqueue_style( 'ithemes-updater-settings-page-style', "{$this->path_url}/css/settings-page.css" );
49
+ }
50
+
51
+ public function index() {
52
+ $post_data = Ithemes_Updater_Functions::get_post_data( array( 'it-updater-username', 'it-updater-password', 'packages', 'action' ), true );
53
+
54
+ if ( empty( $post_data['packages'] ) )
55
+ $post_data['packages'] = array();
56
+
57
+
58
+ $action = $post_data['action'];
59
+
60
+ if ( 'license_packages' == $action )
61
+ $this->license_packages( $post_data );
62
+ else if ( 'unlicense_packages' == $action )
63
+ $this->unlicense_packages( $post_data );
64
+ else if ( 'save_settings' == $action )
65
+ $this->save_settings();
66
+
67
+ $this->list_packages( $action, $post_data );
68
+ }
69
+
70
+ private function get_error_explanation( $error, $package = '' ) {
71
+ $code = $error->get_error_code();
72
+ $package_name = Ithemes_Updater_Functions::get_package_name( $package );
73
+ $message = '';
74
+
75
+ switch( $code ) {
76
+ case 'ITXAPI_Updater_Bad_Login':
77
+ $message = __( 'Incorrect password. Please make sure that you are supplying your iThemes membership username and password details.', 'it-l10n-ithemes-sync' );
78
+ break;
79
+ case 'ITXAPI_Updater_Username_Unknown':
80
+ case 'ITXAPI_Updater_Username_Invalid':
81
+ $message = __( 'Invalid username. Please make sure that you are supplying your iThemes membership username and password details.', 'it-l10n-ithemes-sync' );
82
+ break;
83
+ case 'ITXAPI_Product_Package_Unknown':
84
+ $message = sprintf( __( 'The licensing server reports that the %1$s (%2$s) product is unknown. Please contact support for assistance.', 'it-l10n-ithemes-sync' ), $package_name, $package );
85
+ break;
86
+ case 'ITXAPI_Updater_Too_Many_Sites':
87
+ $message = sprintf( __( '%1$s could not be licensed since the membership account is out of available licenses for this product. You can unlicense the product on other sites or upgrade your membership to one with a higher number of licenses in order to increase the amount of available licenses.', 'it-l10n-ithemes-sync' ), $package_name );
88
+ break;
89
+ case 'ITXAPI_License_Key_Generate_Failed':
90
+ $message = sprintf( __( '%s could not be licensed due to an internal error. Please try to license %s again at a later time. If this problem continues, please contact iThemes support.', 'it-l10n-ithemes-sync' ), $package_name );
91
+ break;
92
+ }
93
+
94
+ if ( empty( $message ) ) {
95
+ if ( ! empty( $package ) )
96
+ $message = sprintf( __( 'An unknown error relating to the %1$s product occurred. Please contact iThemes support. Error details: %2$s', 'it-l10n-ithemes-sync' ), $package_name, $error->get_error_message() . " ($code)" );
97
+ else
98
+ $message = sprintf( __( 'An unknown error occurred. Please contact iThemes support. Error details: %s', 'it-l10n-ithemes-sync' ), $error->get_error_message() . " ($code)" );
99
+ }
100
+
101
+ return $message;
102
+ }
103
+
104
+ private function save_settings() {
105
+ check_admin_referer( 'save_settings', 'ithemes_updater_nonce' );
106
+
107
+
108
+ $settings_defaults = array(
109
+ 'quick_releases' => false,
110
+ );
111
+
112
+ $settings = array();
113
+
114
+ foreach ( $settings_defaults as $var => $val ) {
115
+ if ( isset( $_POST[$var] ) )
116
+ $settings[$var] = $_POST[$var];
117
+ else
118
+ $settings[$var] = $val;
119
+ }
120
+
121
+ if ( $settings['quick_releases'] )
122
+ $settings['quick_releases'] = true;
123
+
124
+ $GLOBALS['ithemes-updater-settings']->update_options( $settings );
125
+
126
+ $GLOBALS['ithemes-updater-settings']->flush( 'settings saved' );
127
+
128
+
129
+ $this->messages[] = __( 'Settings saved', 'it-l10n-ithemes-sync' );
130
+ }
131
+
132
+ private function license_packages( $data ) {
133
+ check_admin_referer( 'license_packages', 'ithemes_updater_nonce' );
134
+
135
+ if ( empty( $data['username'] ) && empty( $data['password'] ) )
136
+ $this->errors[] = __( 'You must supply an iThemes membership username and password in order to license products.', 'it-l10n-ithemes-sync' );
137
+ else if ( empty( $data['username'] ) )
138
+ $this->errors[] = __( 'You must supply an iThemes membership username in order to license products.', 'it-l10n-ithemes-sync' );
139
+ else if ( empty( $data['password'] ) )
140
+ $this->errors[] = __( 'You must supply an iThemes membership password in order to license products.', 'it-l10n-ithemes-sync' );
141
+ else if ( empty( $data['packages'] ) )
142
+ $this->errors[] = __( 'You must select at least one product to license. Ensure that you select the products that you wish to license in the listing below.', 'it-l10n-ithemes-sync' );
143
+
144
+ if ( ! empty( $this->errors ) )
145
+ return;
146
+
147
+
148
+ $response = Ithemes_Updater_API::activate_package( $data['username'], $data['password'], $data['packages'] );
149
+
150
+ if ( is_wp_error( $response ) ) {
151
+ $this->errors[] = $this->get_error_explanation( $response );
152
+
153
+ return;
154
+ }
155
+
156
+ if ( empty( $response['packages'] ) ) {
157
+ $this->errors[] = __( 'An unknown server error occurred. Please try to license your products again at another time.', 'it-l10n-ithemes-sync' );
158
+ return;
159
+ }
160
+
161
+
162
+ uksort( $response['packages'], 'strnatcasecmp' );
163
+
164
+ $success = array();
165
+ $warn = array();
166
+ $fail = array();
167
+
168
+ foreach ( $response['packages'] as $package => $data ) {
169
+ if ( preg_match( '/ \|\|\| \d+$/', $package ) )
170
+ continue;
171
+
172
+ $name = Ithemes_Updater_Functions::get_package_name( $package );
173
+
174
+ if ( ! empty( $data['key'] ) )
175
+ $success[] = $name;
176
+ else if ( ! empty( $data['status'] ) && ( 'expired' == $data['status'] ) )
177
+ $warn[$name] = __( 'Your product subscription has expired', 'it-l10n-ithemes-sync' );
178
+ else
179
+ $fail[$name] = $data['error']['message'];
180
+ }
181
+
182
+
183
+ if ( ! empty( $success ) )
184
+ $this->messages[] = wp_sprintf( __( 'Successfully licensed %l.', 'it-l10n-ithemes-sync' ), $success );
185
+
186
+ if ( ! empty( $fail ) ) {
187
+ foreach ( $fail as $name => $reason )
188
+ $this->errors[] = sprintf( __( 'Unable to license %1$s. Reason: %2$s', 'it-l10n-ithemes-sync' ), $name, $reason );
189
+ }
190
+
191
+ if ( ! empty( $warn ) ) {
192
+ foreach ( $warn as $name => $reason )
193
+ $this->soft_errors[] = sprintf( __( 'Unable to license %1$s. Reason: %2$s', 'it-l10n-ithemes-sync' ), $name, $reason );
194
+ }
195
+ }
196
+
197
+ private function unlicense_packages( $data ) {
198
+ check_admin_referer( 'unlicense_packages', 'ithemes_updater_nonce' );
199
+
200
+ if ( empty( $data['username'] ) && empty( $data['password'] ) )
201
+ $this->errors[] = __( 'You must supply an iThemes membership username and password in order to remove licenses.', 'it-l10n-ithemes-sync' );
202
+ else if ( empty( $data['username'] ) )
203
+ $this->errors[] = __( 'You must supply an iThemes membership username in order to remove licenses.', 'it-l10n-ithemes-sync' );
204
+ else if ( empty( $data['password'] ) )
205
+ $this->errors[] = __( 'You must supply an iThemes membership password in order to remove licenses.', 'it-l10n-ithemes-sync' );
206
+ else if ( empty( $data['packages'] ) )
207
+ $this->errors[] = __( 'You must select at least one license to remove. Ensure that you select the licenses that you wish to remove in the listing below.', 'it-l10n-ithemes-sync' );
208
+
209
+ if ( ! empty( $this->errors ) )
210
+ return;
211
+
212
+
213
+ $response = Ithemes_Updater_API::deactivate_package( $data['username'], $data['password'], $data['packages'] );
214
+
215
+ if ( is_wp_error( $response ) ) {
216
+ $this->errors[] = $this->get_error_explanation( $response );
217
+
218
+ return;
219
+ }
220
+
221
+ if ( empty( $response['packages'] ) ) {
222
+ $this->errors[] = __( 'An unknown server error occurred. Please try to remove licenses from your products again at another time.', 'it-l10n-ithemes-sync' );
223
+ return;
224
+ }
225
+
226
+
227
+ uksort( $response['packages'], 'strnatcasecmp' );
228
+
229
+ $success = array();
230
+ $fail = array();
231
+
232
+ foreach ( $response['packages'] as $package => $data ) {
233
+ if ( preg_match( '/ \|\|\| \d+$/', $package ) )
234
+ continue;
235
+
236
+ $name = Ithemes_Updater_Functions::get_package_name( $package );
237
+
238
+ if ( isset( $data['status'] ) && ( 'inactive' == $data['status'] ) )
239
+ $success[] = $name;
240
+ else if ( isset( $data['error'] ) && isset( $data['error']['message'] ) )
241
+ $fail[$name] = $data['error']['message'];
242
+ else
243
+ $fail[$name] = __( 'Unknown server error.', 'it-l10n-ithemes-sync' );
244
+ }
245
+
246
+
247
+ if ( ! empty( $success ) )
248
+ $this->messages[] = wp_sprintf( _n( 'Successfully removed license from %l.', 'Successfully removed licenses from %l.', count( $success ), 'it-l10n-ithemes-sync' ), $success );
249
+
250
+ if ( ! empty( $fail ) ) {
251
+ foreach ( $fail as $name => $reason )
252
+ $this->errors[] = sprintf( __( 'Unable to remove license from %1$s. Reason: %2$s', 'it-l10n-ithemes-sync' ), $name, $reason );
253
+ }
254
+ }
255
+
256
+ public function list_packages( $action, $post_data ) {
257
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
258
+ $details = Ithemes_Updater_Packages::get_full_details();
259
+ $packages = $details['packages'];
260
+
261
+ $licensed = array();
262
+ $unlicensed = array();
263
+ $unrecognized = array();
264
+
265
+ foreach ( $packages as $path => $data ) {
266
+ $name = Ithemes_Updater_Functions::get_package_name( $data['package'] );
267
+ $data['path'] = $path;
268
+
269
+ if ( 'unlicensed' == $data['status'] )
270
+ $unlicensed[$name] = $data;
271
+ else if ( in_array( $data['status'], array( 'active', 'expired' ) ) )
272
+ $licensed[$name] = $data;
273
+ else
274
+ $unrecognized[$name] = $data;
275
+ }
276
+
277
+
278
+ if ( ! empty( $this->messages ) ) {
279
+ foreach ( $this->messages as $message )
280
+ echo "<div class=\"updated fade\"><p><strong>$message</strong></p></div>\n";
281
+ }
282
+
283
+ if ( ! empty( $this->errors ) ) {
284
+ foreach ( $this->errors as $error )
285
+ echo "<div class=\"error\"><p><strong>$error</strong></p></div>\n";
286
+ }
287
+
288
+ if ( ! empty( $this->soft_errors ) ) {
289
+ foreach ( $this->soft_errors as $error )
290
+ echo "<div class=\"error\"><p><strong>$error</strong></p></div>\n";
291
+ }
292
+
293
+
294
+ ?>
295
+ <div class="wrap">
296
+ <?php
297
+ if ( version_compare( $GLOBALS['wp_version'], '3.7.10', '<=' ) ) {
298
+ screen_icon();
299
+ }
300
+ ?>
301
+
302
+ <h2><?php _e( 'iThemes Licensing', 'it-l10n-ithemes-sync' ); ?></h2>
303
+
304
+ <?php $this->list_licensed_products( $licensed, $post_data, $action ); ?>
305
+
306
+ <?php $this->list_unlicensed_products( $unlicensed, $post_data, $action ); ?>
307
+
308
+ <?php $this->list_unrecognized_products( $unrecognized ); ?>
309
+
310
+ <?php $this->show_settings(); ?>
311
+ </div>
312
+ <?php
313
+
314
+ }
315
+
316
+ private function show_settings() {
317
+ $quick_releases = $GLOBALS['ithemes-updater-settings']->get_option( 'quick_releases' );
318
+
319
+ ?>
320
+ <form id="posts-filter" enctype="multipart/form-data" method="post" action="<?php echo $this->self_url; ?>">
321
+ <?php wp_nonce_field( 'save_settings', 'ithemes_updater_nonce' ); ?>
322
+
323
+ <div id="ithemes-updater-settings">
324
+ <h3 class="subtitle"><?php _e( 'Settings', 'it-l10n-ithemes-sync' ); ?></h3>
325
+
326
+ <table class="form-table">
327
+ <tbody>
328
+ <tr valign="top">
329
+ <th scope="row">
330
+ <label for="quick_releases"><?php _e( 'Quick Release Updates', 'it-l10n-ithemes-sync' ); ?></label>
331
+ </th>
332
+ <td>
333
+ <?php $checked = ( $quick_releases ) ? ' checked="checked"' : ''; ?>
334
+
335
+ <label>
336
+ <input id="quick_releases" type="checkbox" name="quick_releases" value="1" <?php echo $checked; ?>/>
337
+ <?php _e( 'Enable quick release updates', 'it-l10n-ithemes-sync' ); ?>
338
+ </label>
339
+
340
+ <p class="description"><?php _e( 'Some products have quick releases that are created to solve specific issues that some users experience. In order to avoid causing users to have updates show up too frequently, automatic updates to these quick releases are disabled by default. Enabling this feature allows quick releases to be available to the automatic update system. Using this option is only recommended if support has requested that you enable it in order to receive a quick release. You should disable this option at a later time after confirming that the quick release solves the issue for you.', 'it-l10n-ithemes-sync' ); ?></p>
341
+ </td>
342
+ </tr>
343
+ </tbody>
344
+ </table>
345
+
346
+ <p class="submit">
347
+ <input id="save_settings" class="button button-primary" type="submit" value="<?php _e( 'Save Settings', 'it-l10n-ithemes-sync' ); ?>" />
348
+ <input type="hidden" name="action" value="save_settings" />
349
+ </p>
350
+ </div>
351
+ </form>
352
+ <?php
353
+
354
+ }
355
+
356
+
357
+ private function list_licensed_products( $products, $post_data, $action ) {
358
+ if ( empty( $products ) )
359
+ return;
360
+
361
+ uksort( $products, 'strnatcasecmp' );
362
+
363
+ $time = time();
364
+
365
+ $headings = array(
366
+ __( 'Product', 'it-l10n-ithemes-sync' ),
367
+ __( 'Member', 'it-l10n-ithemes-sync' ),
368
+ __( 'Expiration', 'it-l10n-ithemes-sync' ),
369
+ __( 'Remaining Licenses', 'it-l10n-ithemes-sync' ),
370
+ );
371
+
372
+ if ( ( 'unlicense_packages' != $action ) || empty( $this->errors ) ) {
373
+ $post_data = array(
374
+ 'username' => '',
375
+ 'password' => '',
376
+ 'packages' => array(),
377
+ );
378
+ }
379
+
380
+ ?>
381
+ <form id="posts-filter" enctype="multipart/form-data" method="post" action="<?php echo $this->self_url; ?>" autocomplete="off">
382
+ <?php wp_nonce_field( 'unlicense_packages', 'ithemes_updater_nonce' ); ?>
383
+
384
+ <div class="ithemes-updater-products" id="ithemes-updater-licensed">
385
+ <h3 class="subtitle"><?php _e( 'Licensed Products', 'it-l10n-ithemes-sync' ); ?></h3>
386
+
387
+ <table class="ithemes-updater-listing widefat">
388
+ <thead>
389
+ <tr>
390
+ <th id="cb" class="manage-column column-cb check-column" scope="col">
391
+ <label class="screen-reader-text" for="cb-select-all-1"><?php _e( 'Select All' ); ?></label>
392
+ <label>
393
+ <input id="cb-select-all-1" type="checkbox" />
394
+ </label>
395
+ </th>
396
+ <th scope="col">
397
+ <label for="cb-select-all-1"><?php _e( 'Product', 'it-l10n-ithemes-sync' ); ?></label>
398
+ </th>
399
+ <th scope="col"><?php _e( 'Member', 'it-l10n-ithemes-sync' ); ?></th>
400
+ <th scope="col"><?php _e( 'Product Status', 'it-l10n-ithemes-sync' ); ?></th>
401
+ <th scope="col"><?php _e( 'Expiration', 'it-l10n-ithemes-sync' ); ?></th>
402
+ <th scope="col"><?php _e( 'Remaining Licenses', 'it-l10n-ithemes-sync' ); ?></th>
403
+ </tr>
404
+ </thead>
405
+ <tbody>
406
+ <?php $count = 0; ?>
407
+ <?php foreach ( $products as $name => $data ) : ?>
408
+ <?php
409
+ if ( -1 == $data['total'] )
410
+ $remaining = __( 'unlimited', 'it-l10n-ithemes-sync' );
411
+ else
412
+ $remaining = $data['total'] - $data['used'];
413
+
414
+ // if ( 0 == $remaining )
415
+ // $remaining .= ' <a class="button-secondary upgrade">' . __( 'Upgrade', 'it-l10n-ithemes-sync' ) . '</a>';
416
+
417
+
418
+ $expiration = $this->get_expiration_string( $data['expiration'] );
419
+ $expiration = '<time datetime="' . date( 'Y-m-d\TH:i:s\Z', $data['expiration'] ) . '">' . $expiration . '</time>';
420
+
421
+
422
+ $time_left = $data['expiration'] - $time;
423
+ $class = 'expiring';
424
+
425
+ if ( $time_left > ( 86400 * 30 ) )
426
+ $class = 'active';
427
+ else if ( $time_left <= 0 )
428
+ $class = 'expired';
429
+
430
+ if ( 'expired' == $data['status'] ) {
431
+ $class = 'expired';
432
+ $remaining = '&nbsp;';
433
+ }
434
+
435
+
436
+ $status = ucfirst( $class );
437
+
438
+
439
+ if ( ++$count % 2 ) {
440
+ $class .= ' alt';
441
+ }
442
+
443
+
444
+ $check_id = "cb-select-{$data['package']}";
445
+
446
+
447
+ $checked = ( in_array( $data['package'], $post_data['packages'] ) ) ? ' checked' : '';
448
+ ?>
449
+ <tr class="<?php echo $class; ?>">
450
+ <th class="check-column" scope="row">
451
+ <label class="screen-reader-text" for="<?php echo $check_id; ?>"><?php printf( __( 'Select %s' ), $name ); ?></label>
452
+ <label for="<?php echo $check_id; ?>">
453
+ <input id="<?php echo $check_id ?>" name="packages[]" value="<?php echo $data['package']; ?>" type="checkbox"<?php echo $checked; ?>>
454
+ </label>
455
+ </th>
456
+ <td>
457
+ <label for="<?php echo $check_id; ?>"><?php echo $name; ?></label>
458
+ </td>
459
+ <td><?php echo $data['user']; ?></td>
460
+ <td><?php echo $status; ?></td>
461
+ <td><?php echo $expiration; ?></td>
462
+ <td><?php echo $remaining; ?></td>
463
+ </tr>
464
+ <?php endforeach; ?>
465
+ </tbody>
466
+ <tfoot>
467
+ <tr>
468
+ <td colspan="6">
469
+ <input type="text" name="it-updater-username" placeholder="iThemes Username" value="<?php echo esc_attr( $post_data['username'] ); ?>" autocomplete="off" />
470
+ <input type="password" name="it-updater-password" placeholder="Password" value="<?php echo esc_attr( $post_data['password'] ); ?>" />
471
+ <input class="button-primary" type="submit" name="submit" value="<?php _e( 'Remove Licenses', 'it-l10n-ithemes-sync' ); ?>" />
472
+ <input type="hidden" name="action" value="unlicense_packages" />
473
+ </td>
474
+ </tr>
475
+ </tfoot>
476
+ </table>
477
+ </div>
478
+ </form>
479
+ <?php
480
+
481
+ }
482
+
483
+ private function list_unlicensed_products( $products, $post_data, $action ) {
484
+ if ( empty( $products ) )
485
+ return;
486
+
487
+ uksort( $products, 'strnatcasecmp' );
488
+
489
+ if ( ( 'license_packages' != $action ) || empty( $this->errors ) ) {
490
+ $post_data = array(
491
+ 'username' => '',
492
+ 'password' => '',
493
+ 'packages' => array(),
494
+ );
495
+
496
+ foreach ( $products as $name => $data )
497
+ $post_data['packages'][] = $data['package'];
498
+ }
499
+
500
+ ?>
501
+ <form id="posts-filter" enctype="multipart/form-data" method="post" action="<?php echo $this->self_url; ?>" autocomplete="off">
502
+ <?php wp_nonce_field( 'license_packages', 'ithemes_updater_nonce' ); ?>
503
+
504
+ <div class="ithemes-updater-products" id="ithemes-updater-unlicensed">
505
+ <h3 class="subtitle"><?php _e( 'Unlicensed Products', 'it-l10n-ithemes-sync' ); ?></h3>
506
+
507
+ <p><?php _e( 'The following products have not been licensed. Licensing a product gives you access to automatic updates from within WordPress.', 'it-l10n-ithemes-sync' ); ?></p>
508
+ <p><?php _e( 'To license products, select the products you wish to license, enter your iThemes membership username and password, and press the License Products button.', 'it-l10n-ithemes-sync' ); ?></p>
509
+ <p><?php printf( __( 'Need help? <a href="%s">Click here for a quick video tutorial</a>.', 'it-l10n-ithemes-sync' ), 'http://ithemes.com/2013/04/11/introducing-the-new-and-improved-ithemes-licensing-system/' ); ?></p>
510
+
511
+ <table class="ithemes-updater-listing widefat">
512
+ <thead>
513
+ <tr>
514
+ <th id="cb" class="manage-column column-cb check-column" scope="col">
515
+ <label class="screen-reader-text" for="cb-select-all-2"><?php _e( 'Select All' ); ?></label>
516
+ <label>
517
+ <input id="cb-select-all-2" type="checkbox"<?php if ( count( $post_data['packages'] ) == count( $products ) ) echo ' checked'; ?> />
518
+ </label>
519
+ </th>
520
+ <th scope="col">
521
+ <label for="cb-select-all-2"><?php _e( 'Product', 'it-l10n-ithemes-sync' ); ?></label>
522
+ </th>
523
+ </tr>
524
+ </thead>
525
+ <tbody>
526
+ <?php $count = 0; ?>
527
+ <?php foreach ( $products as $name => $data ) : ?>
528
+ <?php
529
+ $check_id = "cb-select-{$data['package']}";
530
+
531
+ if ( 'license_packages' == $action )
532
+ $checked = ( in_array( $data['package'], $post_data['packages'] ) ) ? ' checked' : '';
533
+ else
534
+ $checked = ' checked';
535
+
536
+ if ( ++$count % 2 ) {
537
+ $class = 'alt';
538
+ } else {
539
+ $class = '';
540
+ }
541
+
542
+ ?>
543
+ <tr class="<?php echo $class; ?>">
544
+ <th class="check-column" scope="row">
545
+ <label class="screen-reader-text" for="<?php echo $check_id; ?>"><?php printf( __( 'Select %s' ), $name ); ?></label>
546
+ <label for="<?php echo $check_id; ?>">
547
+ <input id="<?php echo $check_id; ?>" name="packages[]" value="<?php echo $data['package']; ?>" type="checkbox" <?php echo $checked; ?>>
548
+ </label>
549
+ </th>
550
+ <td>
551
+ <label for="<?php echo $check_id; ?>"><?php echo $name; ?></label>
552
+ </td>
553
+ </tr>
554
+ <?php endforeach; ?>
555
+ </tbody>
556
+ <tfoot>
557
+ <tr>
558
+ <td colspan="2">
559
+ <input type="text" name="it-updater-username" placeholder="iThemes Username" value="<?php echo esc_attr( $post_data['username'] ); ?>" autocomplete="off" />
560
+ <input type="password" name="it-updater-password" placeholder="Password" value="<?php echo esc_attr( $post_data['password'] ); ?>" />
561
+ <input class="button-primary" type="submit" name="submit" value="<?php _e( 'License Products', 'it-l10n-ithemes-sync' ); ?>" />
562
+ <input type="hidden" name="action" value="license_packages" />
563
+ </td>
564
+ </tr>
565
+ </tfoot>
566
+ </table>
567
+ </div>
568
+ </form>
569
+ <?php
570
+
571
+ }
572
+
573
+ private function list_unrecognized_products( $products ) {
574
+ if ( empty( $products ) )
575
+ return;
576
+
577
+ uksort( $products, 'strnatcasecmp' );
578
+
579
+ ?>
580
+ <div class="ithemes-updater-products" id="ithemes-updater-unrecognized">
581
+ <h3 class="subtitle"><?php _e( 'Unrecognized Products', 'it-l10n-ithemes-sync' ); ?></h3>
582
+
583
+ <p><?php _e( 'The following products were not recognized by the licensing system. This can be due to a bug in the product code, a temporary server issue, or because the product is no longer supported.', 'it-l10n-ithemes-sync' ); ?></p>
584
+ <p><?php printf( __( 'Please check this page again at a later time to see if the problem resolves itself. If the product remains, please contact <a href="%s">iThemes support</a> and provide them with the details given below.', 'it-l10n-ithemes-sync' ), 'http://ithemes.com/support/' ); ?></p>
585
+
586
+ <table class="ithemes-updater-listing widefat">
587
+ <thead>
588
+ <tr>
589
+ <th scope="col"><?php _e( 'Product', 'it-l10n-ithemes-sync' ); ?></th>
590
+ <th scope="col"><?php _e( 'Type', 'it-l10n-ithemes-sync' ); ?></th>
591
+ <th scope="col"><?php _e( 'Package', 'it-l10n-ithemes-sync' ); ?></th>
592
+ <th scope="col"><?php _e( 'Version', 'it-l10n-ithemes-sync' ); ?></th>
593
+ <th scope="col"><?php _e( 'Server Response', 'it-l10n-ithemes-sync' ); ?></th>
594
+ </tr>
595
+ </thead>
596
+ <tbody>
597
+ <?php $count = 0; ?>
598
+ <?php foreach ( $products as $name => $data ) : ?>
599
+ <?php
600
+ if ( ( 'error' == $data['status'] ) && ( ! empty( $data['error']['message'] ) ) )
601
+ $response = "{$data['error']['message']} ({$data['error']['code']})";
602
+ else
603
+ $response = __( 'Unknown Error', 'it-l10n-ithemes-sync' );
604
+
605
+ if ( ++$count % 2 ) {
606
+ $class = 'alt';
607
+ } else {
608
+ $class = '';
609
+ }
610
+ ?>
611
+ <tr class="<?php echo $class; ?>">
612
+ <td><?php echo $name; ?></td>
613
+ <td><?php echo $data['type']; ?></td>
614
+ <td><?php echo $data['package']; ?></td>
615
+ <td><?php echo $data['installed']; ?></td>
616
+ <td><?php echo $response; ?></td>
617
+ </tr>
618
+ <?php endforeach; ?>
619
+ </tbody>
620
+ </table>
621
+ </div>
622
+ <?php
623
+
624
+ }
625
+
626
+ private function get_expiration_string( $expiration_timestamp ) {
627
+ $time = time();
628
+
629
+ $time_left = $expiration_timestamp - $time;
630
+
631
+ $expired = false;
632
+
633
+ if ( $time_left < 0 ) {
634
+ $expired = true;
635
+ $time_left = abs( $time_left );
636
+ }
637
+
638
+ if ( $time_left > ( 86400 * 30 ) )
639
+ $expiration = date( 'Y-m-d', $expiration_timestamp );
640
+ else {
641
+ if ( $time_left > 86400 )
642
+ $expiration = sprintf( _n( '%d day', '%d days', intval( $time_left / 86400 ), 'it-l10n-ithemes-sync' ), intval( $time_left / 86400 ) );
643
+ else if ( $time_left > 3600 )
644
+ $expiration = sprintf( _n( '%d hour', '%d hours', intval( $time_left / 3600 ), 'it-l10n-ithemes-sync' ), intval( $time_left / 3600 ) );
645
+ else if ( $time_left > 60 )
646
+ $expiration = sprintf( _n( '%d minute', '%d minutes', intval( $time_left / 60 ), 'it-l10n-ithemes-sync' ), intval( $time_left / 60 ) );
647
+ else
648
+ $expiration = sprintf( _n( '%d second', '%d seconds', $time_left, 'it-l10n-ithemes-sync' ), intval( $time_left / 60 ) );
649
+
650
+ if ( $expired )
651
+ $expiration = sprintf( __( '%s ago', 'it-l10n-ithemes-sync' ), $expiration );
652
+ }
653
+
654
+ return $expiration;
655
+ }
656
+ }
657
+
658
+
659
+ new Ithemes_Updater_Settings_Page();
lib/updater/settings.php ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Central management of options storage and registered packages.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.3.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-09-19 - Chris Jean
10
+ Split off from the old Ithemes_Updater_Init class.
11
+ 1.0.1 - 2013-09-20 - Chris Jean
12
+ Fixed bug where the old ithemes-updater-object global was being referenced.
13
+ 1.1.0 - 2013-10-04 - Chris Jean
14
+ Enhancement: Added handler for GET query variable: ithemes-updater-force-minor-update.
15
+ Bug Fix: Changed URL regex for applying the CA patch to only apply to links for api.ithemes.com and not the S3 links.
16
+ Bug Fix: A check to ensure that the $GLOBALS['ithemes_updater_path'] variable is set properly.
17
+ Misc: Updated file reference for ca/cacert.crt to ca/roots.crt.
18
+ 1.2.0 - 2013-10-23 - Chris Jean
19
+ Enhancement: Added the quick_releases setting.
20
+ Enhancement: Added an explicit flush when the ithemes-updater-force-minor-update query variable is used
21
+ Misc: Removed the show_on_sites setting as it is no longer needed.
22
+ 1.3.0 - 2014-10-23 - Chris Jean
23
+ Improved flushing system.
24
+ Reduced cache timeout durations.
25
+ Added timeout multiplier.
26
+ Removed CA patch code as it's now handled in the server code.
27
+ Updated code to meet WordPress coding standards.
28
+ */
29
+
30
+
31
+ class Ithemes_Updater_Settings {
32
+ private $option_name = 'ithemes-updater-cache';
33
+
34
+ private $packages = array();
35
+ private $new_packages = array();
36
+ private $options = false;
37
+ private $options_modified = false;
38
+ private $do_flush = false;
39
+ private $initialized = false;
40
+ private $plugins_cleaned = false;
41
+ private $themes_cleaned = false;
42
+
43
+ private $default_options = array(
44
+ 'timeout-multiplier' => 1,
45
+ 'expiration' => 0,
46
+ 'timestamp' => 0,
47
+ 'packages' => array(),
48
+ 'update_plugins' => array(),
49
+ 'update_themes' => array(),
50
+ 'use_ca_patch' => false,
51
+ 'use_ssl' => true,
52
+ 'quick_releases' => false,
53
+ );
54
+
55
+
56
+ public function __construct() {
57
+ $GLOBALS['ithemes-updater-settings'] = $this;
58
+
59
+ add_action( 'init', array( $this, 'init' ) );
60
+ add_action( 'shutdown', array( $this, 'shutdown' ) );
61
+ }
62
+
63
+ public function init() {
64
+ if ( $this->initialized ) {
65
+ return;
66
+ }
67
+
68
+ $this->initialized = true;
69
+
70
+ if ( ! isset( $GLOBALS['ithemes_updater_path'] ) ) {
71
+ $GLOBALS['ithemes_updater_path'] = dirname( __FILE__ );
72
+ }
73
+
74
+ $this->load();
75
+
76
+ do_action( 'ithemes_updater_register', $this );
77
+
78
+ $this->new_packages = array_diff( array_keys( $this->packages ), $this->options['packages'] );
79
+
80
+
81
+ if ( isset( $_GET['ithemes-updater-force-quick-release-update'] ) && ! isset( $_GET['ithemes-updater-force-minor-update'] ) ) {
82
+ $_GET['ithemes-updater-force-minor-update'] = $_GET['ithemes-updater-force-quick-release-update'];
83
+ }
84
+
85
+
86
+ $flushed = false;
87
+
88
+
89
+ if ( isset( $_GET['ithemes-updater-force-minor-update'] ) && current_user_can( 'manage_options' ) ) {
90
+ if ( $_GET['ithemes-updater-force-minor-update'] ) {
91
+ $this->options['force_minor_version_update'] = time() + 3600;
92
+ $this->update_options( $this->options );
93
+
94
+ $this->flush( 'forced minor version update' );
95
+ $flushed = true;
96
+ } else {
97
+ unset( $this->options['force_minor_version_update'] );
98
+ $this->update_options( $this->options );
99
+
100
+ $this->flush( 'unset forced minor version update' );
101
+ $flushed = true;
102
+ }
103
+ } else if ( isset( $this->options['force_minor_version_update'] ) && ( $this->options['force_minor_version_update'] < time() ) ) {
104
+ unset( $this->options['force_minor_version_update'] );
105
+ $this->update_options( $this->options );
106
+ }
107
+
108
+
109
+ if ( ! $flushed ) {
110
+ if ( ! empty( $_GET['ithemes-updater-force-refresh'] ) && current_user_can( 'manage_options' ) ) {
111
+ $this->flush( 'forced' );
112
+ } else if ( empty( $this->options['expiration'] ) || ( $this->options['expiration'] <= time() ) ) {
113
+ $this->flush( 'expired' );
114
+ } else if ( $this->is_expired( $this->options['timestamp'] ) ) {
115
+ $this->flush( 'got stale' );
116
+ } else if ( ! empty( $this->new_packages ) ) {
117
+ $this->flush( 'new packages' );
118
+ }
119
+ }
120
+ }
121
+
122
+ public function load() {
123
+ if ( false !== $this->options ) {
124
+ return;
125
+ }
126
+
127
+ $this->options = get_site_option( $this->option_name, false );
128
+
129
+ if ( ( false === $this->options ) || ! is_array( $this->options ) ) {
130
+ $this->options = array();
131
+ }
132
+
133
+ $this->options = array_merge( $this->default_options, $this->options );
134
+
135
+ if ( 0 == $this->options['timestamp'] ) {
136
+ $this->update();
137
+ }
138
+ }
139
+
140
+ public function shutdown() {
141
+ $this->flush();
142
+
143
+ if ( $this->options_modified ) {
144
+ update_site_option( $this->option_name, $this->options );
145
+ }
146
+ }
147
+
148
+ public function queue_flush() {
149
+ $this->do_flush = true;
150
+ }
151
+
152
+ public function flush( $reason = '' ) {
153
+ if ( empty( $reason ) && ! $this->do_flush ) {
154
+ return;
155
+ }
156
+
157
+ $this->do_flush = false;
158
+
159
+ $this->update();
160
+ }
161
+
162
+ public function update() {
163
+ $this->init();
164
+
165
+ require_once( $GLOBALS['ithemes_updater_path'] . '/updates.php' );
166
+
167
+ Ithemes_Updater_Updates::run_update();
168
+ }
169
+
170
+ public function get_options() {
171
+ $this->init();
172
+
173
+ return $this->options;
174
+ }
175
+
176
+ public function get_option( $var ) {
177
+ $this->init();
178
+
179
+ if ( isset( $this->options[$var] ) ) {
180
+ return $this->options[$var];
181
+ }
182
+
183
+ return null;
184
+ }
185
+
186
+ public function update_options( $updates ) {
187
+ $this->init();
188
+
189
+ $this->options = array_merge( $this->options, $updates );
190
+ $this->options_modified = true;
191
+ }
192
+
193
+ public function update_packages() {
194
+ $this->update_options( array( 'packages' => array_keys( $this->packages ) ) );
195
+ }
196
+
197
+ public function get_packages() {
198
+ return $this->packages;
199
+ }
200
+
201
+ public function get_new_packages() {
202
+ return $this->new_packages;
203
+ }
204
+
205
+ public function filter_update_plugins( $update_plugins ) {
206
+ if ( ! is_object( $update_plugins ) ) {
207
+ return $update_plugins;
208
+ }
209
+
210
+ if ( ! isset( $update_plugins->response ) || ! is_array( $update_plugins->response ) ) {
211
+ $update_plugins->response = array();
212
+ }
213
+
214
+ $this->flush();
215
+
216
+ if ( ! is_array( $this->options ) || ! isset( $this->options['update_plugins'] ) ) {
217
+ $this->load();
218
+ }
219
+
220
+ if ( isset( $this->options['update_plugins'] ) && is_array( $this->options['update_plugins'] ) ) {
221
+ if ( ! $this->plugins_cleaned ) {
222
+ @include_once( ABSPATH . '/wp-admin/includes/plugin.php' );
223
+
224
+ if ( is_callable( 'get_plugin_data' ) ) {
225
+ foreach ( $this->options['update_plugins'] as $plugin => $update_data ) {
226
+ $plugin_data = get_plugin_data( WP_PLUGIN_DIR . "/$plugin", false, false );
227
+
228
+ if ( $plugin_data['Version'] == $update_data->new_version ) {
229
+ unset( $this->options['update_plugins'][$plugin] );
230
+ $this->plugins_cleaned = true;
231
+ }
232
+ }
233
+ }
234
+
235
+ if ( $this->plugins_cleaned ) {
236
+ $this->options_modified = true;
237
+ }
238
+
239
+ $this->plugins_cleaned = true;
240
+ }
241
+
242
+ $update_plugins->response = array_merge( $update_plugins->response, $this->options['update_plugins'] );
243
+ }
244
+
245
+ return $update_plugins;
246
+ }
247
+
248
+ public function filter_update_themes( $update_themes ) {
249
+ if ( ! is_object( $update_themes ) ) {
250
+ return $update_themes;
251
+ }
252
+
253
+ if ( ! isset( $update_themes->response ) || ! is_array( $update_themes->response ) ) {
254
+ $update_themes->response = array();
255
+ }
256
+
257
+ $this->flush();
258
+
259
+ if ( ! is_array( $this->options ) || ! isset( $this->options['update_themes'] ) ) {
260
+ $this->load();
261
+ }
262
+
263
+ if ( isset( $this->options['update_themes'] ) && is_array( $this->options['update_themes'] ) ) {
264
+ if ( ! $this->themes_cleaned ) {
265
+ foreach ( $this->options['update_themes'] as $theme => $update_data ) {
266
+ $theme_data = wp_get_theme( $theme );
267
+
268
+ if ( $theme_data->get( 'Version' ) == $update_data['new_version'] ) {
269
+ unset( $this->options['update_themes'][$theme] );
270
+ $this->themes_cleaned = true;
271
+ }
272
+ }
273
+
274
+ if ( $this->themes_cleaned ) {
275
+ $this->options_modified = true;
276
+ }
277
+
278
+ $this->themes_cleaned = true;
279
+ }
280
+
281
+ $update_themes->response = array_merge( $update_themes->response, $this->options['update_themes'] );
282
+ }
283
+
284
+ return $update_themes;
285
+ }
286
+
287
+ public function register( $slug, $file ) {
288
+ $this->packages[$slug][] = $file;
289
+ }
290
+
291
+ private function is_expired( $timestamp ) {
292
+ $multiplier = $this->get_option( 'timeout-multiplier' );
293
+
294
+ if ( $multiplier < 1 ) {
295
+ $multiplier = 1;
296
+ } else if ( $multiplier > 10 ) {
297
+ $multiplier = 10;
298
+ }
299
+
300
+
301
+ if ( current_user_can( 'update_themes' ) || current_user_can( 'update_plugins' ) ) {
302
+ if ( ! empty( $_POST['action'] ) && ( 'heartbeat' == $_POST['action'] ) ) {
303
+ $timeout = 43200;
304
+ } else {
305
+ $page = empty( $_GET['page'] ) ? $GLOBALS['pagenow'] : $_GET['page'];
306
+
307
+ switch ( $page ) {
308
+ case 'update.php' :
309
+ case 'update-core.php' :
310
+ case 'ithemes-licensing' :
311
+ $timeout = 60;
312
+ break;
313
+ case 'plugins.php' :
314
+ case 'themes.php' :
315
+ $timeout = 600;
316
+ break;
317
+ default :
318
+ $timeout = 3600;
319
+ }
320
+ }
321
+ } else {
322
+ $timeout = 7200;
323
+ }
324
+
325
+ $timeout *= $multiplier;
326
+
327
+
328
+ if ( $timestamp <= ( time() - $timeout ) ) {
329
+ return true;
330
+ }
331
+
332
+ return false;
333
+ }
334
+ }
335
+
336
+ new Ithemes_Updater_Settings();
lib/updater/updates.php ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Provides a simple interface for connecting iThemes' packages with the updater API.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.4.1
7
+
8
+ Version History
9
+ 1.0.0 - 2013-04-11 - Chris Jean
10
+ Release ready
11
+ 1.0.1 - 2013-09-19 - Chris Jean
12
+ Changed the logic in process_server_response to skip updatable packages that have the 'upgrade' data set to a true value.
13
+ Updated requires to not use dirname().
14
+ Updated ithemes-updater-object to ithemes-updater-settings.
15
+ 1.1.0 - 2013-10-02 - Chris Jean
16
+ Updated 'url' data for themes to point to the plugin-install.php file in order to show changelog notes as plugins have.
17
+ 1.2.0 - 2013-10-04 - Chris Jean
18
+ Added logic to handle skipped updates when force_minor_version_update is set.
19
+ 1.2.1 - 2013-10-04 - Chris Jean
20
+ Added a fix to prevent the code from executing if it is loaded by an older updater version. This can happen when updating a theme or plugin.
21
+ 1.3.0 - 2013-10-23 - Chris Jean
22
+ Enhancement: Added support for quick_releases setting to force an update to a quick release.
23
+ 1.4.0 - 2014-11-13 - Chris Jean
24
+ Improved cache flush handling.
25
+ Removed server-cache setting change handler.
26
+ Added timeout-multiplier setting change handler.
27
+ 1.4.1 - 2015-04-23 - Chris Jean
28
+ Added "plugin" entry for plugins in order to handle changes in WordPress 4.2.
29
+ Added "theme" entry for themes in order to handle changes in WordPress 4.2.
30
+ Added support for both "autoupdate" and "upgrade_notice" fields to be supplied from the server.
31
+ */
32
+
33
+
34
+ class Ithemes_Updater_Updates {
35
+ public static function run_update() {
36
+ // Prevent the code from running if the code was loaded by an older updater version.
37
+ if ( ! isset( $GLOBALS['ithemes_updater_path'] ) ) {
38
+ return;
39
+ }
40
+
41
+ require_once( $GLOBALS['ithemes_updater_path'] . '/api.php' );
42
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
43
+ require_once( $GLOBALS['ithemes_updater_path'] . '/keys.php' );
44
+
45
+
46
+ $keys = Ithemes_Updater_Keys::get();
47
+ $legacy_keys = Ithemes_Updater_Keys::get_legacy();
48
+
49
+ if ( empty( $keys ) && empty( $legacy_keys ) ) {
50
+ return;
51
+ }
52
+
53
+
54
+ Ithemes_Updater_API::get_package_details( false );
55
+ }
56
+
57
+ public static function process_server_response( $response, $cached = false ) {
58
+ if ( empty( $response['packages'] ) ) {
59
+ return;
60
+ }
61
+
62
+
63
+ require_once( $GLOBALS['ithemes_updater_path'] . '/keys.php' );
64
+ require_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
65
+
66
+
67
+ $keys = array();
68
+
69
+ foreach ( $response['packages'] as $package => $data ) {
70
+ if ( isset( $data['key'] ) ) {
71
+ $keys[$package] = $data['key'];
72
+ } else if ( isset( $data['status'] ) && ( 'inactive' == $data['status'] ) ) {
73
+ $keys[$package] = '';
74
+ }
75
+ }
76
+
77
+ Ithemes_Updater_Keys::set( $keys );
78
+
79
+
80
+ $details = Ithemes_Updater_Packages::get_full_details( $response );
81
+
82
+ $updates = array(
83
+ 'update_themes' => array(),
84
+ 'update_plugins' => array(),
85
+ 'expiration' => $details['expiration'],
86
+ );
87
+
88
+ if ( ! $cached ) {
89
+ $updates['timestamp'] = time();
90
+ }
91
+
92
+
93
+ if ( isset( $response['timeout_multiplier'] ) ) {
94
+ $updates['timeout-multiplier'] = $response['timeout_multiplier'];
95
+ }
96
+
97
+ if ( ! isset( $updates['timeout-multiplier'] ) || ( $updates['timeout-multiplier'] < 1 ) ) {
98
+ $updates['timeout-mulitplier'] = 1;
99
+ } else if ( $updates['timeout-multiplier'] > 10 ) {
100
+ $updates['timeout-mulitplier'] = 10;
101
+ }
102
+
103
+ $use_ssl = $GLOBALS['ithemes-updater-settings']->get_option( 'use_ssl' );
104
+
105
+
106
+ foreach ( $details['packages'] as $path => $data ) {
107
+ if ( empty( $data['package-url'] ) || version_compare( $data['installed'], $data['available'], '>=' ) ) {
108
+ continue;
109
+ }
110
+
111
+
112
+ $force_minor_version_update = $GLOBALS['ithemes-updater-settings']->get_option( 'force_minor_version_update' );
113
+ $quick_releases = $GLOBALS['ithemes-updater-settings']->get_option( 'quick_releases' );
114
+
115
+ if ( ( isset( $data['upgrade'] ) && ! $data['upgrade'] ) && ! $force_minor_version_update && ! $quick_releases ) {
116
+ continue;
117
+ }
118
+
119
+ if ( ! $use_ssl ) {
120
+ $data['package-url'] = preg_replace( '/^https/', 'http', $data['package-url'] );
121
+ }
122
+
123
+ if ( 'plugin' == $data['type'] ) {
124
+ $update = array(
125
+ 'id' => 0,
126
+ 'slug' => dirname( $path ),
127
+ 'plugin' => $path,
128
+ 'new_version' => $data['available'],
129
+ 'url' => $data['info-url'],
130
+ 'package' => $data['package-url'],
131
+ );
132
+
133
+ if ( isset( $data['autoupdate'] ) ) {
134
+ $update['autoupdate'] = $data['autoupdate'];
135
+ }
136
+ if ( isset( $data['upgrade_notice'] ) ) {
137
+ $update['upgrade_notice'] = $data['upgrade_notice'];
138
+ }
139
+
140
+ $update = (object) $update;
141
+ }
142
+ else {
143
+ $update = array(
144
+ 'theme' => $path,
145
+ 'new_version' => $data['available'],
146
+ 'url' => self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . dirname( $path ) . '&section=changelog&TB_iframe=true&width=600&height=800' ),
147
+ 'package' => $data['package-url'],
148
+ );
149
+
150
+ if ( isset( $data['autoupdate'] ) ) {
151
+ $update['autoupdate'] = $data['autoupdate'];
152
+ }
153
+ if ( isset( $data['upgrade_notice'] ) ) {
154
+ $update['upgrade_notice'] = $data['upgrade_notice'];
155
+ }
156
+
157
+ $path = dirname( $path );
158
+ }
159
+
160
+ $updates["update_{$data['type']}s"][$path] = $update;
161
+ }
162
+
163
+
164
+ $GLOBALS['ithemes-updater-settings']->update_options( $updates );
165
+ }
166
+ }
load-translations.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Loads up the translations for the plugin.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-06 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2015-08-18 - Chris Jean
12
+ Enhancement: Added support for translations being located outside the plugin.
13
+ Bug Fix: Changed plugin textdomain path from absolute to relative.
14
+ */
15
+
16
+
17
+ $ithemes_sync_plugin_dir = basename( $GLOBALS['ithemes_sync_path'] );
18
+ $ithemes_sync_locale = apply_filters( 'plugin_locale', get_locale(), 'it-l10n-ithemes-sync' );
19
+
20
+ load_textdomain( 'it-l10n-ithemes-sync', WP_LANG_DIR . "/plugins/ithemes-sync/it-l10n-ithemes-sync-$ithemes_sync_locale.mo" );
21
+ load_plugin_textdomain( 'it-l10n-ithemes-sync', false, "$ithemes_sync_plugin_dir/lang/" );
load.php ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Load the Sync plugin components.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.1
7
+
8
+ Version History
9
+ 1.0.0 - 2014-03-26 - Chris Jean
10
+ Created from version 1.3.5 of init.php.
11
+ 1.0.1 - 2014-10-13 - Chris Jean
12
+ Added more robust checking of the $_GET['ithemes-sync-request'] data.
13
+ 1.1.0 - 2014-11-07 - Chris Jean
14
+ Added a admin-ajax.php handler for sending Sync requests.
15
+ 1.1.1 - 2014-11-21 - Chris Jean
16
+ Removed call to stripslashes_deep() in AJAX handler.
17
+ */
18
+
19
+ function ithemes_sync_handle_ajax_request() {
20
+ require_once( $GLOBALS['ithemes_sync_path'] . '/request-handler.php' );
21
+ }
22
+ add_action( 'wp_ajax_nopriv_ithemes_sync_request', 'ithemes_sync_handle_ajax_request' );
23
+
24
+ if ( ! empty( $_GET['ithemes-sync-request'] ) ) {
25
+ require_once( $GLOBALS['ithemes_sync_path'] . '/request-handler.php' );
26
+ }
27
+
28
+ if ( is_admin() ) {
29
+ require( $GLOBALS['ithemes_sync_path'] . '/admin.php' );
30
+ }
31
+
32
+ require_once( $GLOBALS['ithemes_sync_path'] . '/client-dashboard.php' );
33
+ require_once( $GLOBALS['ithemes_sync_path'] . '/social.php' );
34
+ require_once( $GLOBALS['ithemes_sync_path'] . '/notices.php' );
35
+ require_once( $GLOBALS['ithemes_sync_path'] . '/duplicator.php' );
36
+
37
+ /**
38
+ * Add a notice to be sent to the server when it makes a status or notice request.
39
+ *
40
+ * This function only sends notices to the server during the ithemes_sync_add_notices action. If a notice must be sent
41
+ * to the server outside of this time, use the ithemes_sync_urgent_notice() function.
42
+ *
43
+ * @since 1.4.0
44
+ *
45
+ * @param string $source Uniquely identifies the project that is sending the notice.
46
+ * @param string $id Identifies the type of notice. This is to allow the server to differentiate different kinds of
47
+ * notices without having to parse the message.
48
+ * @param string $subject A brief subject description of the notice that is fit for presentation to Sync users.
49
+ * @param string $message A message that is fit for presentation to Sync users.
50
+ * @param array $data Optional. Data that is relevant to the notice. For notices that may be best presented in a
51
+ * graphical manner, this field could be used to send data used to construct the graphic.
52
+ * @return bool Currently, it always returns true.
53
+ */
54
+ function ithemes_sync_add_notice( $source, $id, $subject, $message, $data = array() ) {
55
+ require_once( $GLOBALS['ithemes_sync_path'] . '/notice-handler.php' );
56
+
57
+ return $GLOBALS['ithemes_sync_notice_handler']->add_notice( $source, $id, $subject, $message, $data );
58
+ }
59
+
60
+ /**
61
+ * Send an urgent notice to the Sync server.
62
+ *
63
+ * This function sends notices to the server immediately.
64
+ *
65
+ * @since 1.4.0
66
+ *
67
+ * @param string $source Uniquely identifies the project that is sending the notice.
68
+ * @param string $id Identifies the type of notice. This is to allow the server to differentiate different kinds of
69
+ * notices without having to parse the message.
70
+ * @param string $subject A brief subject description of the notice that is fit for presentation to Sync users.
71
+ * @param string $message A message that is fit for presentation to Sync users.
72
+ * @param array $data Optional. Data that is relevant to the notice. For notices that may be best presented in a
73
+ * graphical manner, this field could be used to send data used to construct the graphic.
74
+ * @return bool Currently, it always returns true.
75
+ */
76
+ function ithemes_sync_send_urgent_notice( $source, $id, $subject, $message, $data = array() ) {
77
+ require_once( $GLOBALS['ithemes_sync_path'] . '/notice-handler.php' );
78
+
79
+ return $GLOBALS['ithemes_sync_notice_handler']->send_urgent_notice( $source, $id, $subject, $message, $data );
80
+ }
81
+
82
+ function ithemes_sync_handle_activation_hook() {
83
+ set_site_transient( 'ithemes-sync-activated', true, 120 );
84
+ delete_site_option( 'ithemes_sync_hide_authenticate_notice' );
85
+ }
86
+ register_activation_hook( __FILE__, 'ithemes_sync_handle_activation_hook' );
87
+
88
+ function ithemes_sync_handle_deactivation_hook() {
89
+ delete_site_option( 'ithemes_sync_hide_authenticate_notice' );
90
+ delete_site_transient( 'ithemes-sync-activated' );
91
+ }
92
+ register_deactivation_hook( __FILE__, 'ithemes_sync_handle_deactivation_hook' );
93
+
94
+
95
+ function ithemes_sync_updater_register( $updater ) {
96
+ $updater->register( 'ithemes-sync', __FILE__ );
97
+ }
98
+ add_action( 'ithemes_updater_register', 'ithemes_sync_updater_register' );
99
+
100
+ require( $GLOBALS['ithemes_sync_path'] . '/lib/updater/load.php' );
101
+
102
+ /*
103
+ * Whitelist the Sync Server IP addresses in iThemes Security to prevent
104
+ * Sync from being blacklisted by a customer on their own site
105
+ */
106
+ function ithemes_sync_itsec_white_ips( $white_ips ) {
107
+ $white_ips[] = '54.163.229.119';
108
+ $white_ips[] = '54.159.83.156';
109
+ return $white_ips;
110
+ }
111
+ add_filter( 'itsec_white_ips', 'ithemes_sync_itsec_white_ips' );
112
+
113
+ function ithemes_sync_login() {
114
+ if ( !empty( $_GET['sync-login'] ) ) {
115
+ $option = get_option( 'sync_login_' . $_GET['sync-login'] );
116
+ delete_option( 'sync_login_' . $_GET['sync-login'] );
117
+ $current_time = current_time( 'timestamp', true );
118
+
119
+ if ( $option['expires'] > $current_time ) {
120
+ wp_set_current_user( $option['user_id'] );
121
+ wp_set_auth_cookie( $option['user_id'], false, is_ssl() );
122
+ }
123
+
124
+ if ( !empty( $option['path'] ) ) {
125
+ switch( $option['path'] ) {
126
+ case 'addpost':
127
+ if ( empty( $option['path_data'] ) ) {
128
+ $post_type = 'post';
129
+ } else {
130
+ $post_type = $option['path_data'];
131
+ }
132
+ $path = 'post-new.php?post_type=' . $option['path_data'];
133
+ break;
134
+ case 'editpost':
135
+ $path = 'post.php?post=' . $option['path_data'] .'&action=edit';
136
+ break;
137
+ case 'duplicatepost':
138
+ list( $post_id, $post_type ) = explode( '-', $option['path_data'] );
139
+ $path = 'post-new.php?post_type=' . $post_type .'&ithemes-sync-duplicate-post-id=' . $post_id;
140
+ break;
141
+ case 'addpage':
142
+ $path = 'post-new.php?post_type=page';
143
+ break;
144
+ default:
145
+ $path = '';
146
+ }
147
+ } else {
148
+ $path = '';
149
+ }
150
+
151
+ wp_redirect( admin_url( $path ) );
152
+ }
153
+ return false;
154
+ }
155
+ add_action( 'init', 'ithemes_sync_login' );
156
+
157
+ function ithemes_sync_daily_schedule() {
158
+ global $wpdb;
159
+
160
+ $results = $wpdb->get_results(
161
+ $wpdb->prepare( "
162
+ SELECT option_name, option_value
163
+ FROM $wpdb->options
164
+ WHERE option_name LIKE %s
165
+ ",
166
+ 'sync_login_%' )
167
+ );
168
+
169
+ $current_time = current_time( 'timestamp', true );
170
+ foreach ( $results as $result ) {
171
+ $data = maybe_unserialize( $result->option_value );
172
+ if ( $data['expires'] < $current_time ) {
173
+ delete_option( $result->option_name );
174
+ }
175
+ }
176
+ }
177
+ add_action( 'ithemes_sync_daily_schedule', 'ithemes_sync_daily_schedule' );
178
+
179
+ function ithemes_sync_google_site_verification() {
180
+ $option = get_option( 'ithemes-sync-googst' );
181
+ if ( !empty( $option ) ) {
182
+ echo "\n\n" . $option['code'] . "\n\n";
183
+ if ( $option['expiry'] <= time() ) {
184
+ delete_option( 'ithemes-sync-googst' );
185
+ }
186
+ }
187
+ }
188
+ add_action('wp_head', 'ithemes_sync_google_site_verification');
notice-handler.php ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Offers interface to add and retrieve notices.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-03-28 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ require_once( $GLOBALS['ithemes_sync_path'] . '/load-translations.php' );
15
+
16
+
17
+ class Ithemes_Sync_Notice_Handler {
18
+ private $urgent_notice_cache_option_name = 'ithemes_sync_urgent_notice_cache';
19
+ private $notice_timestamps_option_name = 'ithemes_sync_notice_timestamps';
20
+
21
+ private $notices = array();
22
+
23
+ public function __construct() {
24
+ $GLOBALS['ithemes_sync_notice_handler'] = $this;
25
+ }
26
+
27
+ public function add_notice( $source, $id, $subject, $message, $data = array() ) {
28
+ $this->notices[] = compact( 'source', 'id', 'subject', 'message', 'data' );
29
+
30
+ return true;
31
+ }
32
+
33
+ public function get_notices( $arguments ) {
34
+ do_action( 'ithemes_sync_add_notices', $arguments );
35
+
36
+ $notice_timestamps = get_site_option( $this->notice_timestamps_option_name, array() );
37
+ $new_notice_timestamps = array();
38
+
39
+ foreach ( $this->notices as $index => $notice ) {
40
+ if ( isset( $notice_timestamps[$notice['source']] ) && isset( $notice_timestamps[$notice['source']][$notice['id']] ) ) {
41
+ $timestamp = $notice_timestamps[$notice['source']][$notice['id']];
42
+ } else {
43
+ $timestamp = time();
44
+ }
45
+
46
+ $new_notice_timestamps[$notice['source']][$notice['id']] = $timestamp;
47
+
48
+ $this->notices[$index]['timestamp'] = $timestamp;
49
+ }
50
+
51
+ update_site_option( $this->notice_timestamps_option_name, $new_notice_timestamps );
52
+
53
+ return $this->notices;
54
+ }
55
+
56
+ public function send_urgent_notice( $source, $id, $subject, $message, $data = array() ) {
57
+ require_once( $GLOBALS['ithemes_sync_path'] . '/server.php' );
58
+ require_once( $GLOBALS['ithemes_sync_path'] . '/settings.php' );
59
+
60
+ $timestamp = time();
61
+
62
+ $notice = compact( 'source', 'id', 'subject', 'message', 'data', 'timestamp' );
63
+
64
+ $notices = $this->get_urgent_notices();
65
+ $notices[] = $notice;
66
+
67
+ $options = $GLOBALS['ithemes-sync-settings']->get_options();
68
+
69
+ $errors = false;
70
+ foreach ( $options['authentications'] as $user_id => $user ) {
71
+ $result = Ithemes_Sync_Server::send_urgent_notices( $user_id, $user['username'], $user['key'], $notices );
72
+
73
+ if ( ! is_wp_error( $result ) && is_array( $result ) && !empty( $result['success'] ) ) {
74
+ continue;
75
+ } else {
76
+ $errors = true;
77
+ $this->set_urgent_notices( $notices );
78
+ break;
79
+ }
80
+ }
81
+
82
+ if ( !empty( $errors ) ) {
83
+ return $result;
84
+ } else {
85
+ $this->clear_urgent_notices();
86
+ return true;
87
+ }
88
+
89
+ return new WP_Error( 'unknown-response', __( 'The Sync server returned an unknown response.', 'it-l10n-ithemes-sync' ) );
90
+ }
91
+
92
+ public function get_urgent_notices() {
93
+ return get_site_option( $this->urgent_notice_cache_option_name, array() );
94
+ }
95
+
96
+ public function set_urgent_notices( $notices ) {
97
+ update_site_option( $this->urgent_notice_cache_option_name, $notices );
98
+ }
99
+
100
+ public function clear_urgent_notices() {
101
+ delete_site_option( $this->urgent_notice_cache_option_name );
102
+ }
103
+
104
+ }
105
+
106
+ new Ithemes_Sync_Notice_Handler();
notices.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Ithemes_Sync_Notices {
4
+
5
+ function __construct() {
6
+ if ( empty( $GLOBALS['ithemes_sync_request_handler'] ) ) {
7
+ /* WordPress Core */
8
+ add_action( '_core_updated_successfully', array( $this, 'core_updated_successfully' ) );
9
+
10
+ /* Plugins */
11
+ add_action( 'activated_plugin', array( $this, 'activated_plugin' ), 10, 2 );
12
+ add_action( 'deactivated_plugin', array( $this, 'deactivated_plugin' ), 10, 2 );
13
+ add_action( 'delete_plugin', array( $this, 'delete_plugin' ), 10 );
14
+ add_action( 'deleted_plugin', array( $this, 'deleted_plugin' ), 10, 2 );
15
+
16
+ /* Themes */
17
+ add_action( 'switch_theme', array( $this, 'switch_theme' ), 10, 2 );
18
+ add_action( 'delete_site_transient_update_themes', array( $this, 'delete_site_transient_update_themes' ) ); //Theme Deleted
19
+
20
+ /* Plugins and Themes */
21
+ add_action( 'upgrader_process_complete', array( $this, 'upgrader_process_complete' ), 10, 2 );
22
+
23
+ /* Backup Buddy */
24
+ add_action( 'backupbuddy_core_add_notification', array( $this, 'backupbuddy_core_add_notification' ) );
25
+ add_action( 'backupbuddy_run_remote_snapshot_response', array( $this, 'backupbuddy_run_remote_snapshot_response' ) );
26
+
27
+ /* iThemes Security */
28
+ add_action( 'itsec_log_event', array( $this, 'itsec_log_event' ), 10, 8 );
29
+ }
30
+ }
31
+
32
+ function backupbuddy_core_add_notification( $notification ) {
33
+ if ( !empty( $notification['slug'] ) && 'backup_success' == $notification['slug'] ) {
34
+ ithemes_sync_send_urgent_notice( 'backupbuddy', 'report', $notification['title'], $notification['message'], $notification );
35
+ }
36
+ }
37
+
38
+ function backupbuddy_run_remote_snapshot_response( $response ) {
39
+ if ( !empty( $response['success'] ) ) {
40
+ $response['timestamp'] = time();
41
+ $response['slug'] = 'live_snapshot_success';
42
+ ithemes_sync_send_urgent_notice( 'backupbuddy', 'report', 'Snapshot Initiated', 'BackupBuddy Live Snapshot Initiated Successfully', $response );
43
+ }
44
+ }
45
+
46
+ function itsec_log_event( $module, $priority, $data, $host, $username, $user, $url, $referrer ) {
47
+ if ( !empty( $data ) && is_array( $data ) ) {
48
+ if ( isset( $data['query_string'] ) && empty( $data['query_string'] ) ) {
49
+ return;
50
+ }
51
+ ithemes_sync_send_urgent_notice( 'ithemes-security', 'report', 'iThemes Security', 'iThemes Security', $data );
52
+ }
53
+ }
54
+
55
+ function core_updated_successfully( $wp_version ) {
56
+ $data['slug'] = 'wordpress_core_updated';
57
+ $data['version'] = $wp_version;
58
+ ithemes_sync_send_urgent_notice( 'wordpress-core', 'report', 'WordPress Updated', 'WordPress Updated', $data );
59
+ }
60
+
61
+ function activated_plugin( $plugin_basename, $network_deactivating ) {
62
+ $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_basename, true, false );
63
+ $data['slug'] = 'wordpress_plugin_activated';
64
+ ithemes_sync_send_urgent_notice( 'wordpress-plugin', 'report', 'Plugin Activated', 'Plugin Activated', $data );
65
+ }
66
+
67
+ function deactivated_plugin( $plugin_basename, $network_deactivating ) {
68
+ $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_basename, true, false );
69
+ $data['slug'] = 'wordpress_plugin_deactivated';
70
+ ithemes_sync_send_urgent_notice( 'wordpress-plugin', 'report', 'Plugin Deactivated', 'Plugin Deactivated', $data );
71
+ }
72
+
73
+ function delete_plugin( $plugin_file ) {
74
+ if ( empty( $_REQUEST['action'] ) || 'delete-selected' !== $_REQUEST['action'] || empty( $_REQUEST['checked'] ) ) {
75
+ return;
76
+ }
77
+
78
+ $plugin_slug = dirname( $plugin_file );
79
+ $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file, true, false );
80
+
81
+ $deleted_plugins = get_option( 'sync_wp_deleted_plugins', array() );
82
+ $deleted_plugins[$plugin_file] = $data;
83
+ update_option( 'sync_wp_deleted_plugins', $deleted_plugins );
84
+ }
85
+
86
+ function deleted_plugin( $plugin_file, $deleted ) {
87
+ $deleted_plugins = get_option( 'sync_wp_deleted_plugins', array() );
88
+ if ( !empty( $deleted_plugins[$plugin_file] ) ) {
89
+ $data = $deleted_plugins[$plugin_file];
90
+ unset( $deleted_plugins[$plugin_file] );
91
+ update_option( 'sync_wp_deleted_plugins', $deleted_plugins );
92
+ }
93
+ if ( $deleted ) {
94
+ $data['slug'] = 'wordpress_plugin_uninstalled';
95
+ ithemes_sync_send_urgent_notice( 'wordpress-plugin', 'report', 'Plugins Uninstalled', 'Plugins Uninstalled', $data );
96
+ }
97
+ }
98
+
99
+ function switch_theme( $new_name, $new_theme ) {
100
+ if ( empty( $new_theme ) ) {
101
+ return;
102
+ }
103
+
104
+ $data = array();
105
+ $data['slug'] = 'wordpress_theme_activated';
106
+ $data['name'] = $new_theme->get( 'Name' );
107
+ $data['version'] = $new_theme->get( 'Version' );
108
+ ithemes_sync_send_urgent_notice( 'wordpress-theme', 'report', 'Theme Activated', 'Theme Activated', $data );
109
+ }
110
+
111
+ function delete_site_transient_update_themes( $transient ) {
112
+ if ( empty( $_GET['stylesheet'] ) ) {
113
+ return;
114
+ }
115
+
116
+ $data = array();
117
+ $data['slug'] = 'wordpress_theme_uninstalled';
118
+ $data['name'] = $_GET['stylesheet'];
119
+ ithemes_sync_send_urgent_notice( 'wordpress-theme', 'report', 'Theme Uninstalled', 'Theme Uninstalled', $data );
120
+ }
121
+
122
+ function upgrader_process_complete( $upgrader, $extra ) {
123
+ if ( empty( $extra['type'] ) ) {
124
+ return;
125
+ }
126
+
127
+ if ( 'plugin' === $extra['type'] ) {
128
+ if ( 'install' === $extra['action'] ) {
129
+ if ( ! $slug = $upgrader->plugin_info() ) {
130
+ return;
131
+ }
132
+
133
+ $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $slug, true, false );
134
+ $data['slug'] = 'wordpress_plugin_installed';
135
+ ithemes_sync_send_urgent_notice( 'wordpress-plugin', 'report', 'Plugin Installed', 'Plugin Installed', $data );
136
+ }
137
+ if ( 'update' === $extra['action'] ) {
138
+ if ( !empty( $extra['bulk'] ) && true == $extra['bulk'] ) {
139
+ $slugs = $extra['plugins'];
140
+ } else {
141
+ if ( empty( $upgrader->skin->plugin ) ) {
142
+ return;
143
+ }
144
+ $slugs = array( $upgrader->skin->plugin );
145
+ }
146
+
147
+ foreach ( $slugs as $slug ) {
148
+ $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $slug, true, false );
149
+ $data['slug'] = 'wordpress_plugin_updated';
150
+ ithemes_sync_send_urgent_notice( 'wordpress-plugin', 'report', 'Plugin Updated', 'Plugin Updated', $data );
151
+ }
152
+ }
153
+ } else if ( 'theme' === $extra['type'] ) {
154
+ if ( 'install' === $extra['action'] ) {
155
+ $theme = $upgrader->theme_info();
156
+ if ( ! $theme ) {
157
+ return;
158
+ }
159
+ $data = array();
160
+ $data['slug'] = 'wordpress_theme_installed';
161
+ $data['name'] = $theme->get( 'Name' );
162
+ $data['version'] = $theme->get( 'Version' );
163
+ ithemes_sync_send_urgent_notice( 'wordpress-theme', 'report', 'Theme Installed', 'Theme Installed', $data );
164
+ }
165
+ if ( 'update' === $extra['action'] ) {
166
+ if ( !empty( $extra['bulk'] ) && true == $extra['bulk'] ) {
167
+ $slugs = $extra['themes'];
168
+ } else {
169
+ if ( empty( $upgrader->skin->theme ) ) {
170
+ return;
171
+ }
172
+ $slugs = array( $upgrader->skin->theme );
173
+ }
174
+ foreach ( $slugs as $slug ) {
175
+ $data = array();
176
+ $data['slug'] = 'wordpress_theme_updated';
177
+ $theme = wp_get_theme( $slug );
178
+ $data['name'] = $theme->get( 'Name' );
179
+ $data['version'] = $theme->get( 'Version' );
180
+ ithemes_sync_send_urgent_notice( 'wordpress-theme', 'report', 'Theme Updated', 'Theme Updated', $data );
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ }
187
+ new Ithemes_Sync_Notices();
readme.txt ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === iThemes Sync ===
2
+ Contributors: ithemes, layotte, blepoxp
3
+ Tags: manage multiple Sites, backup, security, migrate, SEO, manage updates, administration, update manager, reports, sync, google analytics, optimize, uptime, ithemes, customize dashboard, client sites, maintenance, management, google webmaster tools, reporting
4
+ Requires at least: 4.5
5
+ Tested up to: 4.8
6
+ Stable tag: 2.0.0
7
+ License: GPLv3 or later
8
+ License URI: http://www.gnu.org/licenses/quick-guide-gplv3.html
9
+
10
+ Manage multiple WordPress sites from one dashboard.
11
+
12
+ == Description ==
13
+
14
+ = Manage Multiple WordPress Sites From One Dashboard =
15
+
16
+ Manage updates (and much more!) for all your WordPress websites from one central dashboard. With iThemes Sync, there’s no more logging in to multiple websites to perform WordPress admin tasks. Let iThemes Sync be your personal WordPress website assistant with features for WordPress management productivity. See all [20+ ways iThemes Sync will save you time in your WordPress workflow](https://ithemes.com/2015/06/17/20-ways-ithemes-sync-will-save-you-time/).
17
+
18
+ = Maintained and Supported by iThemes =
19
+
20
+ iThemes has been building WordPress tools since 2008, including iThemes Security, our [WordPress security plugin](http://ithemes.com/security) and BackupBuddy, our [WordPress backup plugin](http://ithemes.com/purchase/backupbuddy)
21
+
22
+ = One Central Dashboard to Save You Loads of Time =
23
+ iThemes Sync allows you to manage and maintain multiple WordPress websites from one central dashboard. Instead of having to login to each individual website, iTheme Sync gives you one place to view important website data and perform remote WordPress admin actions. Easily switch to iThemes Sync’s individual site view to perform site-specific admin actions such as running WordPress plugin and theme updates, making remote WordPress backups and more.
24
+
25
+ * No More Logging In To Multiple WordPress Websites - Logging in to multiple websites is tedious and time-consuming. No more keeping up with multiple usernames and passwords. With Sync, you have one dashboard for all your WordPress websites.
26
+ * Perform WordPress Admin Actions Remotely - iThemes Sync allows you to perform a host of admin tasks remotely—like installing themes and plugins, managing comments, running updates, adding users and much more.
27
+ * Stay On Top of WordPress Updates - WordPress updates are important both for the security and efficiency of WordPress websites. iThemes Sync totally changes the way you update your WordPress sites—now you can update dozens of sites with click.
28
+ * Bulk Install Themes and Plugins From WordPress.org or Zip Upload - Search and install from WordPress.org and view your WordPress.org profile favorites. Upload theme or plugin zip files or install themes and plugins directly from your iThemes membership.
29
+ * WordPress User Manager - WordPress user management in iThemes Sync allows you view all users on a website, edit user profiles and delete users remotely. With Sync’s Client Dashboard, you can customize how certain users see the WordPress dashboard.
30
+ * WordPress Post & Page Manager - View a list of current posts and pages on your site, including the title, author, date last modified, focus keyword, SEO score and SEO readability.
31
+ * Duplicate Pages & Posts Remotely - Once clicked, you’ll be taken to a new, duplicate post or page on your website.
32
+ * Remotely Login to WordPress & Switch Between User Accounts - Quickly jump to the WordPress Admin dashboard of your WordPress site where you’ll be instantly logged in from iThemes Sync. You can also switch between any WordPress user account registered on the site without sharing password info.
33
+ * WordPress Comment Manager - View all comments (all, approved, pending, spam, trash) and unapprove, mark as spam, or move comments to trash.
34
+ * WordPress Database Optimizations (Pro) - Make sure your WordPress sites are running efficiently with WordPress optimizations. Sync handles actions like cleaning out post revisions, spam comments and more.
35
+ * WordPress Uptime Monitoring (Pro) - Sync Pro offers a way to monitor WordPress uptime, downtime & overall performance for all your WordPress websites. Get notification emails when sites go down and view a history of your overall WordPress performance.
36
+ * SEO Checker + Google Analytics & Google Search Console Reports (Pro) - Track key SEO metrics with Sync's [SEO Checker](https://ithemes.com/2016/11/09/yoast-seo-checker-ithemes-sync/) and Google Analytics & Google Search Console integration. Sync integrates with the Yoast SEO plugin to give you an overview of the health of your SEO efforts.
37
+ * Remote WordPress Backups & WordPress Security Actions - Run remote backups with Sync's integration with BackupBuddy, our WordPress backup plugin, and perform more WordPress security actions from Sync with iThemes Security, our WordPress security plugin.
38
+
39
+ = Features Designed for Freelancers and Agencies =
40
+ Sync Pro was made for anyone managing multiple WordPress sites. If you're building sites for clients, you'll love how Sync makes caring for client sites easier and more profitable.
41
+
42
+ * WordPress Maintenance Reports - Sync Pro includes in-depth WordPress maintenance reporting to show the work involved in maintaining WordPress websites. Email clients beautiful HTML email reports with a summary of update actions you took to keep their site running smoothly.
43
+ * Customize the WordPress dashboard for clients/users (Pro) - With Sync Pro, you get Client Dashboard, a WordPress role manager that controls which menu items your clients see in their WordPress dashboard. Don't want them have access to plugins, themes or settings? Simplify their dashboard so they only see what they need -- like posts and pages.
44
+ * Show/Hide Sync From the WP Dashboard - To keep your clients or other site users from messing with your Sync authentication or removing the plugin, you can choose to hide or show the plugin in the WordPress dashboard of each site you manage with Sync.
45
+ * Share Your Sync Dashboard with Another User - If you have other people on your team that you'd like to task with helping you maintain sites, this feature is a great way help you easily delegate your workload to other team members.
46
+ * Site Notes and Tags - Sync’s Notes feature is a handy way to keep notations about your Synced sites. For example, if you added customizations to theme or plugin files that could be lost with an update, add a reminder. Tag business sites, personal sites, client sites, live, non-live or however you want to be able identify your sites.
47
+
48
+ = WordPress Security & Monitoring =
49
+
50
+ * Two-factor Authentication - Add an extra layer of protection to your Sync dashboard login with two-factor authentication. Once enabled, Sync will require both a password AND an SMS code sent to your mobile device to log in to the Sync Dashboard.
51
+ * WordPress Uptime Monitoring - Monitor WordPress uptime and know immediately if one of your websites goes down with uptime notification emails. View uptime stats such as total uptime percentage, total downtime, and number of downtimes for WordPress performance monitoring.
52
+ * Whitelist Your IP Address (iThemes Security integration) - Temporarily whitelist your IP Address remotely so you don't get locked out of your website by the iThemes Security plugin's Bad Users feature.
53
+ * Release iThemes Security Lockouts (iThemes Security integration) - View and release any current iThemes Security lockouts.
54
+
55
+ = WordPress Updates =
56
+
57
+ * Daily Notification Emails - Get a daily summary of available WordPress updates as a helpful reminder to log in and run updates. Customize the delivery time your daily notification emails to fit your schedule.
58
+ * One-Click Update All - View a summary of all available updates for all your websites and then update them all with one click.
59
+ * View Plugin Changelogs - View update logs that include all updates made in Sync, including the date of the update, previous version number and new version number.
60
+ * View Updates by Site - The in-depth individual site view gives you a detailed view of the available updates for that site, with more WordPress site management action options.
61
+ * Sort Updates By Theme or Plugin - View a listing of individual theme and plugins you have installed on your websites along with available update information.
62
+ * Ignore Update Option - If you've made customizations to your themes and plugins, an update could overwrite your work. Choose to ignore updates for specific themes and plugins.
63
+
64
+ = WordPress Maintenance Reports for Clients (Pro) =
65
+ Make steady, reliable income for WordPress maintenance with iThemes Sync Pro’s WordPress Maintenance Reports.
66
+
67
+ * Show Clients the Value of What You Do - Detailed website reports are one of the best ways to show clients the value of a monthly [WordPress maintenance](https://ithemes.com/sync/wordpress-maintenance/) plan. Sync Pro's WordPress Maintenance Reports handle the hard work of compiling all the update actions you took, so you can justify the value of what you do.
68
+ * White-label Report Emails - Upload your own logo and set custom colors for the header, footer and button color of report emails to better match your company’s branding.
69
+ * Summarize Update Actions to WordPress Core, Themes & Plugins + More Website Stats - Reports include all update actions taken within a certain timeframe, both from the Sync dashboard and the WordPress dashboard. [See a full list of all of stats included in Sync's WordPress reports](https://ithemes.com/sync/wordpress-report-stats/).
70
+ * Schedule Reports to Automatically Run & Deliver Report Emails - Reports can run on a daily, weekly or monthly basis, and then automatically email reports to clients.
71
+ * Email Beautiful, Interactive HTML Reports to Clients - Reports are delivered as beautiful, interactive HTML reports so clients can get a visual summary of the WordPress maintenance you provide. Sync handles compiling and organizing update actions and key website stats. [See a WordPress maintenance report demo](https://ithemes.com/sync-pro/demo-report.html).
72
+ * SEO Reporting - Summarize key SEO Stats stats in an easy-to-digest format — showing how your client’s sites are doing in Google and where they can improve. [See a WordPress SEO report demo](https://ithemes.com/sync-pro/seo-demo-report.html).
73
+
74
+ = Integrations =
75
+
76
+ * WordPress.org - Search and install themes and plugins from WordPress.org remotely from Sync. View your WordPress.org profile favorites to save time.
77
+ * BackupBuddy - Run remote WordPress backups, download your latest backup file, view number of edits since your last BackupBuddy backup and download ImportBuddy remotely.
78
+ * BackupBuddy Stash - View, download and delete your BackupBuddy Stash backups, and view a graph of the total amount of Stash space you've used.
79
+ * iThemes Security - Temporarily Whitelist your IP Address remotely so you don't get locked out of the website. View and release any iThemes Security lockouts.
80
+ * Google Analytics - Track Google Analytics for multiple WordPress sites. View key metrics from Google Analytics such as traffic, landing pages, keywords, sessions and referrer URLS.
81
+ * Google Search Console - Get a quick summary of Google Search Console data, view search analytics, crawl errors and sitemaps info.
82
+ * Twitter - Automatically share posts and pages to Twitter. Once activated, adds a button to your WordPress post editor to tweet.
83
+ * Yoast SEO - Sync's SEO Checker feature integrates with the Yoast SEO Plugin so you can quickly see the SEO status of the content on your WordPress website.
84
+ * Gravity Forms - From Sync, you can view all the forms you’ve created in Gravity Forms for that site. You can also quickly see how many responses and views each form has received.
85
+ * WP101 Plugin - Easily add a complete series of WordPress onboarding tutorial videos to the WordPress dashboard of your client sites with Sync's WP101 plugin integration.
86
+
87
+ == Changelog ==
88
+
89
+ = 2.0.0 =
90
+ * Initial Release to wordpress.org
91
+
92
+ == Installation ==
93
+
94
+ 1. Signup for your [free iThemes Sync 10 site plan](https://sync.ithemes.com/signup).
95
+ 2. Follow the normal WordPress plugin installation method to install the iThemes Sync plugin on your WordPress website.
96
+ 3. Follow the steps to add your first website to iThemes Sync.
97
+
98
+ For more detailed instructions, check out the [Sync Quick Setup Guide](https://ithemes.com/2013/11/11/how-to-set-up-ithemes-sync/).
99
+
100
+ == Screenshots ==
101
+
102
+ 1. iThemes Sync dashboard home screen with multiple WordPress websites
103
+ 2. Individual site view with remote WordPress admin actions
104
+ 3. Updates view with listing of themes/plugins and available site updates
105
+ 4. Bulk install themes and plugins across multiple sites via zip upload and WordPress.org search
106
+ 5. Uptime monitoring (Pro)
107
+ 6. Run remote backups and download backup files remotely with BackupBuddy integration
108
+ 7. Update sites on the go with mobile-optimized dashboard
109
+ 8. Activate Client Dashboard for certain users. Customize the WordPress dashboard and hide/display menu items (Pro)
110
+ 9. Send clients interactive WordPress maintenance reports that summarize update actions and other key site metrics (Pro)
111
+
112
+
113
+ == License ==
114
+
115
+ This file is part of iThemes Sync. This plugin is released under the terms of the GNU General Public License.
116
+
117
+ DISCLAIMER: Under no circumstances do we release this plugin with any warranty, implied or otherwise. We cannot be held responsible for any damage that might arise from the use of this plugin.
118
+
119
+ == Frequently Asked Questions ==
120
+
121
+ = Is iThemes Sync free? =
122
+
123
+ You can manage 10 sites totally free for iThemes Sync. If you need additional sites, you can [add more sites to your Sync plan](https://ithemes.com/sync). Some features including Client Dashboard and WordPress Maintenance Reports are Pro-only features and not included in the 10-site free plan.
124
+
125
+ = Do you offer support for free users? =
126
+
127
+ Yes. We handle support for free iThemes Sync users both from the WordPress.org forum and from the [iThemes Help Desk](https://ithemes.com/support). The iThemes Help Desk offers private, ticketed support to protect your privacy.
128
+
129
+ = Is iThemes Sync secure? =
130
+
131
+ Yes. We develop and maintain iThemes Sync according to strict WordPress security standards. We take security seriously; so much so, we have an in-house WordPress security team and have our own WordPress security plugin with over 1 million active installs.
132
+
133
+ To help keep access to iThemes Sync secure, we strongly suggest enabling two-factor authentication for your iTheme Sync account. Using two-factor authentication for your Sync login will help ensure that only you have access to the websites in Sync. Because iThemes Sync allows you to perform WordPress administrator tasks such as installing and uninstalling themes and plugins, adding WordPress user accounts, logging into the WordPress dashboard and more, securing your iThemes Sync account is as important as securing your WordPress administrator account login.
134
+
135
+ = Does iThemes Sync work with WordPress.com sites? =
136
+
137
+ No. iThemes Sync only works with self-hosted WordPress sites.
138
+
139
+ = Help! I’m having problems adding a site to Sync. =
140
+
141
+ iThemes Sync includes an automatic “Add Site" prompt when you first login to the Sync dashboard. If this authentication method fails, try manually installing the iThemes Sync plugin on your WordPress site. Make sure to [create an iThemes account here](https://sync.ithemes.com/signup) and then follow the prompt from the Sync plugin to add your credentials. If you still need help, please open a ticket at the [iThemes Help Desk](https://ithemes.com/support).
142
+
143
+
144
+ Got more questions? [Contact us!](https://ithemes.com/contact “iThemes Contact")
request-handler.php ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Handle requests from Sync server.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.3.5
7
+
8
+ Version History
9
+ 1.2.0 - 2014-01-20 - Chris Jean
10
+ Changed send_response() from private to public to allow for sending responses from error handlers.
11
+ 1.2.1 - 2014-02-18 - Chris Jean
12
+ Added a compatibility check to ensure that Gravity Forms's updates show up and can be applied.
13
+ Added a function to fake that the request is taking place on an admin page. This is rudimentary and won't work for every situation.
14
+ 1.2.2 - 2014-02-19 - Chris Jean
15
+ Changed method_exists to is_callable in order to avoid server-specific compatibility issues.
16
+ 1.3.0 - 2014-02-21 - Chris Jean
17
+ Renamed hide_errors() to restore_error_settings() as that's a more accurate name.
18
+ Added hide_errors() to hide all error output.
19
+ hide_errors() is called when the response is done rendering to prevent stray output from messing up the server's parsing of the output.
20
+ 1.3.1 - 2014-03-06 - Chris Jean
21
+ Rearranged permission-escalation code to after the request is authenticated.
22
+ Sync requests will now be set to emulate an Administrator user to avoid checks by some security plugins.
23
+ Added set_full_user_capabilities(), unset_full_user_capabilities(), and filter_user_has_cap().
24
+ 1.3.2 - 2014-08-22 - Chris Jean
25
+ In order to avoid stale data, external object caches are now disabled on all authenticated requests.
26
+ 1.3.3 - 2014-08-25 - Chris Jean
27
+ Disable two-factor authentication checks from the Duo Two-Factor Authentication plugin when an authenticated request is being handled.
28
+ 1.3.4 - 2014-10-13 - Chris Jean
29
+ Added stronger verification of the $_POST['request'] data.
30
+ Obfuscated the missing-var error responses.
31
+ 1.3.5 - 2014-11-21 - Chris Jean
32
+ Added smarter use of stripslashes() to avoid issues in decoding utf8 characters.
33
+ Moved validation check of $_POST['request'] to the constructor in order to better handle both forms of requests (legacy and admin-ajax.php).
34
+ */
35
+
36
+
37
+ require_once( $GLOBALS['ithemes_sync_path'] . '/load-translations.php' );
38
+
39
+ class Ithemes_Sync_Request_Handler {
40
+ private $logs = array();
41
+ private $options = array();
42
+ private $old_update_data = array();
43
+ private $verb_time = false;
44
+
45
+
46
+ public function __construct() {
47
+ $this->show_errors();
48
+
49
+
50
+ if ( empty( $_POST['request'] ) ) {
51
+ return;
52
+ }
53
+
54
+ $request = $_POST['request'];
55
+
56
+ if ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || get_magic_quotes_gpc() ) {
57
+ $request = stripslashes( $request );
58
+ }
59
+
60
+ $request = json_decode( $request, true );
61
+
62
+ if ( ! is_array( $request ) ) {
63
+ return;
64
+ }
65
+
66
+
67
+ $GLOBALS['ithemes_sync_request_handler'] = $this;
68
+
69
+
70
+ add_action( 'ithemes-sync-add-log', array( $this, 'add_log' ), 10, 2 );
71
+ add_action( 'shutdown', array( $this, 'handle_error' ) );
72
+
73
+ add_action( 'ithemes_sync_verbs_registered', array( $this, 'handle_request' ) );
74
+
75
+ require_once( $GLOBALS['ithemes_sync_path'] . '/api.php' );
76
+ require_once( $GLOBALS['ithemes_sync_path'] . '/functions.php' );
77
+ require_once( $GLOBALS['ithemes_sync_path'] . '/settings.php' );
78
+
79
+ $this->options = $GLOBALS['ithemes-sync-settings']->get_options();
80
+
81
+ $this->parse_request( $request );
82
+
83
+ Ithemes_Sync_Functions::set_time_limit( 60 );
84
+
85
+ $this->set_is_admin_to_true();
86
+ $this->set_current_user_to_admin();
87
+ $this->set_full_user_capabilities();
88
+ $this->disable_ext_object_cache();
89
+ $this->disable_2fa_verification();
90
+ }
91
+
92
+ private function show_errors() {
93
+ $this->original_display_errors = ini_set( 'display_errors', 1 );
94
+ $this->original_error_reporting = error_reporting( E_ALL );
95
+ }
96
+
97
+ private function restore_error_settings() {
98
+ ini_set( 'display_errors', $this->original_display_errors );
99
+ error_reporting( $this->original_error_reporting );
100
+ }
101
+
102
+ private function hide_errors() {
103
+ ini_set( 'display_errors', 0 );
104
+ error_reporting( null );
105
+ }
106
+
107
+ private function set_is_admin_to_true() {
108
+ if ( defined( 'ITHEMES_SYNC_SKIP_SET_IS_ADMIN_TO_TRUE' ) && ITHEMES_SYNC_SKIP_SET_IS_ADMIN_TO_TRUE ) {
109
+ return;
110
+ }
111
+
112
+ if ( ! defined( 'WP_ADMIN' ) ) {
113
+ define( 'WP_ADMIN', true );
114
+ }
115
+ }
116
+
117
+ private function disable_ext_object_cache() {
118
+ // This disables object caching that many caching plugins offer which prevents the cache from supplying stale data to Sync.
119
+ if ( is_callable( 'wp_using_ext_object_cache' ) ) {
120
+ wp_using_ext_object_cache( false );
121
+ }
122
+ }
123
+
124
+ private function disable_2fa_verification() {
125
+ // Disable 2FA verification of the Duo Two-Factor Authentication plugin.
126
+ add_filter( 'pre_site_option_duo_ikey', array( $this, 'return_empty_string' ) );
127
+ add_filter( 'pre_option_duo_ikey', array( $this, 'return_empty_string' ) );
128
+ }
129
+
130
+ private function set_current_user_to_admin() {
131
+ if ( ! class_exists( 'WP_Roles' ) ) {
132
+ do_action( 'ithemes-sync-add-log', 'The WP_Roles class does not exist. Unable to set current user to admin.' );
133
+ return false;
134
+ }
135
+
136
+ $wp_roles = new WP_Roles();
137
+
138
+ if ( ! isset( $wp_roles->roles ) ) {
139
+ do_action( 'ithemes-sync-add-log', 'Unable to find user roles. Unable to set current user to admin.', compact( 'wp_roles' ) );
140
+ return false;
141
+ }
142
+
143
+ $roles = $wp_roles->roles;
144
+
145
+ $max_caps = 0;
146
+ $power_role = false;
147
+
148
+ foreach ( $roles as $role => $role_data ) {
149
+ if ( ! isset( $role_data['capabilities'] ) ) {
150
+ continue;
151
+ }
152
+
153
+ $cap_count = count( $role_data['capabilities'] );
154
+ $new_role = false;
155
+
156
+ if ( $cap_count > $max_caps ) {
157
+ $power_role = $role;
158
+ $max_caps = $cap_count;
159
+ } else if ( ( $cap_count == $max_caps ) && ( 'administrator' == $role ) ) {
160
+ $power_role = $role;
161
+ $max_caps = $cap_count;
162
+ }
163
+ }
164
+
165
+ if ( false === $power_role ) {
166
+ if ( isset( $roles['administrator'] ) ) {
167
+ $power_role = 'administrator';
168
+ } else {
169
+ $role_names = array_keys( $roles );
170
+ $power_role = $roles[0];
171
+ }
172
+ }
173
+
174
+ if ( false === $power_role ) {
175
+ do_action( 'ithemes-sync-add-log', 'Unable to find a power user role. Unable to set current user to admin.', compact( 'wp_roles' ) );
176
+ return false;
177
+ }
178
+
179
+
180
+ if ( ! function_exists( 'get_users' ) ) {
181
+ do_action( 'ithemes-sync-add-log', 'get_users() function does not exist. Unable to set current user to admin.' );
182
+ return false;
183
+ }
184
+
185
+ $users = get_users( array( 'role' => $power_role ) );
186
+
187
+ if ( ! is_array( $users ) ) {
188
+ do_action( 'ithemes-sync-add-log', 'get_users() retured a non-array. Unable to set current user to admin.', $users );
189
+ return false;
190
+ }
191
+
192
+ $auth_details = $GLOBALS['ithemes-sync-settings']->get_authentication_details( $this->request['user_id'] );
193
+
194
+ foreach( $users as $u ) {
195
+ if ( $u->data->user_login === $auth_details['local_user'] ) {
196
+ //Prioritize the Sync user first, if it doesn't match for some reason, we'll fall back to any administrator user
197
+ $user = $u;
198
+ break;
199
+ } else {
200
+ $user = $u;
201
+ }
202
+ }
203
+
204
+ if ( isset( $user->ID ) ) {
205
+ $GLOBALS['current_user'] = $user;
206
+ } else {
207
+ do_action( 'ithemes-sync-add-log', 'Unable to find a valid user object for the power user role. Unable to set current user to admin.', $user );
208
+ return false;
209
+ }
210
+
211
+ return true;
212
+ }
213
+
214
+ private function set_full_user_capabilities() {
215
+ add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 1000, 3 );
216
+ }
217
+
218
+ private function unset_full_user_capabilities() {
219
+ remove_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 1000 );
220
+ }
221
+
222
+ public function filter_user_has_cap( $capabilities, $caps, $args ) {
223
+ foreach ( $caps as $cap ) {
224
+ $capabilities[$cap] = 1;
225
+ }
226
+
227
+ return $capabilities;
228
+ }
229
+
230
+ private function parse_request( $request ) {
231
+ if ( empty( $this->options['authentications'] ) ) {
232
+ $this->send_response( new WP_Error( 'site-not-authenticated', 'The site does not have any authenticated users.' ) );
233
+ }
234
+
235
+ $this->request = $request;
236
+
237
+ $required_vars = array(
238
+ '1' => 'action',
239
+ '2' => 'arguments',
240
+ '3' => 'user_id',
241
+ '4' => 'hash',
242
+ '5' => 'salt',
243
+ );
244
+
245
+ foreach ( $required_vars as $index => $var ) {
246
+ if ( ! isset( $request[$var] ) ) {
247
+ $this->send_response( new WP_Error( "missing-var-$index", 'Invalid request.' ) );
248
+ }
249
+ }
250
+
251
+ if ( ! isset( $this->options['authentications'][$request['user_id']] ) ) {
252
+ $this->send_response( new WP_Error( 'user-not-authenticated', 'The requested user is not authenticated.' ) );
253
+ }
254
+
255
+ $user_data = $this->options['authentications'][$request['user_id']];
256
+
257
+ $hash = hash( 'sha256', $request['user_id'] . $request['action'] . $this->json_encode( $request['arguments'] ) . $user_data['key'] . $request['salt'] );
258
+
259
+ if ( $hash !== $request['hash'] ) {
260
+ $this->send_response( new WP_Error( 'hash-mismatch', 'The hash could not be validated as a correct hash.' ) );
261
+ }
262
+ }
263
+
264
+ public function handle_request() {
265
+ $this->add_third_party_compatibility();
266
+ $this->disable_updater_transient_pre_filters();
267
+ $this->add_old_plugin_updater_support();
268
+
269
+ $start_time = microtime( true );
270
+ $results = $GLOBALS['ithemes-sync-api']->run( $this->request['action'], $this->request['arguments'] );
271
+ $this->verb_time = microtime( true ) - $start_time;
272
+
273
+ $this->send_response( $results );
274
+ }
275
+
276
+ public function send_response( $data ) {
277
+ if ( is_wp_error( $data ) ) {
278
+ foreach ( $data->get_error_codes() as $code )
279
+ $response['errors'][$code] = $data->get_error_message( $code );
280
+ }
281
+ else {
282
+ $response = array(
283
+ 'response' => $data,
284
+ );
285
+ }
286
+
287
+ if ( ! empty( $this->logs ) ) {
288
+ $response['logs'] = $this->logs;
289
+ }
290
+
291
+ $response['verb_time'] = $this->verb_time;
292
+ $json = $this->json_encode( $response );
293
+
294
+ echo "\n\nv56CHRcOT+%K\$fk[*CrQ9B5<~9T=h?xx9C</`Sqv;M{Q0ms:FR0w\n\n$json";
295
+
296
+ $this->hide_errors();
297
+
298
+ remove_action( 'shutdown', array( $this, 'handle_error' ) );
299
+
300
+ exit;
301
+ }
302
+
303
+ private function add_third_party_compatibility() {
304
+ if ( is_callable( array( 'RGForms', 'check_update' ) ) ) {
305
+ add_filter( 'transient_update_plugins', array( 'RGForms', 'check_update' ) );
306
+ add_filter( 'site_transient_update_plugins', array( 'RGForms', 'check_update' ) );
307
+ }
308
+ }
309
+
310
+ private function disable_updater_transient_pre_filters() {
311
+ // Avoid conflicts with plugins that pre-filter the update transients.
312
+ add_filter( 'pre_site_transient_update_plugins', array( $this, 'return_false' ), 9999 );
313
+ add_filter( 'pre_site_transient_update_themes', array( $this, 'return_false' ), 9999 );
314
+ add_filter( 'pre_site_transient_update_core', array( $this, 'return_false' ), 9999 );
315
+ }
316
+
317
+ public function return_false() {
318
+ return false;
319
+ }
320
+
321
+ private function add_old_plugin_updater_support() {
322
+ $plugins = Ithemes_Sync_Functions::get_plugin_details();
323
+
324
+ $data['3.0'] = get_site_transient( 'update_plugins' );
325
+ $data['2.8'] = get_transient( 'update_plugins' );
326
+ $data['2.6'] = get_option( 'update_plugins' );
327
+
328
+ foreach ( array( '2.8', '2.6' ) as $version ) {
329
+ if ( is_object( $data[$version] ) && ! empty( $data[$version]->response ) ) {
330
+ foreach ( $data[$version]->response as $plugin => $plugin_data ) {
331
+ if ( ! empty( $data['3.0']->response[$plugin] ) || ! empty( $this->old_update_data['plugins'][$plugin] ) ) {
332
+ continue;
333
+ }
334
+
335
+ if ( ! empty( $plugins[$plugin] ) && ! empty( $plugins[$plugin]['Version'] ) && version_compare( $plugin_data->new_version, $plugins[$plugin]['Version'], '<=' ) ) {
336
+ continue;
337
+ }
338
+
339
+ $this->old_update_data['plugins'][$plugin] = $plugin_data;
340
+ }
341
+ }
342
+ }
343
+
344
+ if ( empty( $this->old_update_data['plugins'] ) ) {
345
+ return;
346
+ }
347
+
348
+
349
+ add_filter( 'site_transient_update_plugins', array( $this, 'filter_update_plugins_add_old_update_data' ) );
350
+ }
351
+
352
+ public function filter_update_plugins_add_old_update_data( $update_plugins ) {
353
+ if ( ! isset( $update_plugins->response ) || ! is_array( $update_plugins->response ) ) {
354
+ return $update_plugins;
355
+ }
356
+
357
+ foreach ( $this->old_update_data['plugins'] as $plugin => $plugin_data ) {
358
+ if ( ! empty( $update_plugins->response[$plugin] ) )
359
+ continue;
360
+
361
+ $plugin_data->from_old_update_data = true;
362
+ $update_plugins->response[$plugin] = $plugin_data;
363
+ }
364
+
365
+ return $update_plugins;
366
+ }
367
+
368
+ public function remove_old_update_plugins_data( $plugin ) {
369
+ if ( empty( $this->old_update_data['plugins'] ) || ! isset( $this->old_update_data['plugins'][$plugin] ) ) {
370
+ return null;
371
+ }
372
+
373
+ $data['2.8'] = get_transient( 'update_plugins' );
374
+ $data['2.6'] = get_option( 'update_plugins' );
375
+
376
+ $found_match = array();
377
+
378
+ foreach ( array( '2.8', '2.6' ) as $version ) {
379
+ $found_match[$version] = false;
380
+
381
+ if ( is_object( $data[$version] ) && ! empty( $data[$version]->response ) && isset( $data[$version]->response[$plugin] ) ) {
382
+ unset( $data[$version]->response[$plugin] );
383
+ $found_match[$version] = true;
384
+ }
385
+
386
+ if ( empty( $data[$version]->response ) && ( 1 == count( get_object_vars( $data[$version] ) ) ) ) {
387
+ $data[$version] = false;
388
+ }
389
+ }
390
+
391
+ if ( $found_match['2.8'] ) {
392
+ if ( false === $data['2.8'] ) {
393
+ delete_transient( 'update_plugins' );
394
+ } else {
395
+ update_transient( 'update_plugins', $data['2.8'] );
396
+ }
397
+ }
398
+
399
+ if ( $found_match['2.6'] ) {
400
+ if ( false === $data['2.6'] ) {
401
+ delete_option( 'update_plugins' );
402
+ } else {
403
+ update_option( 'update_plugins', $data['2.6'] );
404
+ }
405
+ }
406
+
407
+
408
+ return ( $found_match['2.8'] || $found_match['2.6'] );
409
+ }
410
+
411
+ public function add_log( $description, $data = 'nD{k*v8}Qn4x=_7/j&r83cGD?%GWk}wb6[xal[9;y`PfpLSY[7O>b' ) {
412
+ if ( is_wp_error( $description ) ) {
413
+ $description = array(
414
+ 'type' => 'WP_Error',
415
+ );
416
+
417
+ $codes = $description->get_error_codes();
418
+ $messages = $description->get_error_messages();
419
+
420
+ if ( 1 == count( $codes ) ) {
421
+ $description['code'] = current( $codes );
422
+ $description['message'] = current( $messages );
423
+ } else {
424
+ $description['codes'] = $codes;
425
+ $description['messages'] = $messages;
426
+ }
427
+ }
428
+
429
+ $log['description'] = $description;
430
+
431
+ if ( 'nD{k*v8}Qn4x=_7/j&r83cGD?%GWk}wb6[xal[9;y`PfpLSY[7O>b' != $data ) {
432
+ $log['data'] = $data;
433
+ }
434
+
435
+ $this->logs[] = $log;
436
+ }
437
+
438
+ public function handle_error() {
439
+ $this->send_response( new WP_Error( 'unhandled_request', 'This request was not handled by any registered verb. This was likely caused by a fatal error.' ) );
440
+ }
441
+
442
+ public function return_empty_string() {
443
+ return '';
444
+ }
445
+
446
+ private function json_encode( $data ) {
447
+ $json = json_encode( $data );
448
+
449
+ if ( false !== $json ) {
450
+ return $json;
451
+ }
452
+
453
+ require_once( $GLOBALS['ithemes_sync_path'] . '/class-ithemes-sync-json.php' );
454
+
455
+ return Ithemes_Sync_JSON::encode( $data );
456
+ }
457
+ }
458
+
459
+ new Ithemes_Sync_Request_Handler();
server.php ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Provides an easy to use interface for communicating with the iThemes Sync server.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.0.1 - 2013-02-14 - Chris Jean
12
+ Changed the request timeout to 30 seconds to accomodate sites that have longer delays when communicating with the Sync server.
13
+ 1.1.0 - 2014-03-28 - Chris Jean
14
+ Added ping() and send_urgent_notices() function.
15
+ 1.1.1 - 2014-04-15 - Chris Jean
16
+ Changed the reported WordPress version from using $wp_version to Ithemes_Sync_Functions::get_wordpress_version().
17
+ 1.1.2 - 2014-06-26 - Chris Jean
18
+ Changed the WordPress version used to generate the salted password from using $wp_version to Ithemes_Sync_Functions::get_wordpress_version().
19
+ 1.2.0 - 2014-09-30 - Chris Jean
20
+ Added new password hashing via the iThemes_Credentials class.
21
+ */
22
+
23
+
24
+ class Ithemes_Sync_Server {
25
+ private static $secure_server_url = 'https://sync-api.ithemes.com/';
26
+ private static $insecure_server_url = 'http://sync-api.ithemes.com/';
27
+
28
+ private static $password_iterations = 8;
29
+
30
+
31
+ public static function authenticate( $username, $password ) {
32
+ $query = array(
33
+ 'user' => $username,
34
+ );
35
+
36
+ $data = array(
37
+ 'auth_token' => self::get_password_hash( $username, $password ),
38
+ );
39
+
40
+ return self::request( 'authenticate-user', $query, $data );
41
+ }
42
+
43
+ public static function deauthenticate( $user_id, $username, $private_key ) {
44
+ $query = array(
45
+ 'user_id' => $user_id,
46
+ 'user' => $username,
47
+ );
48
+
49
+ $salt = hash( 'sha256', uniqid( '', true ) );
50
+
51
+ $data = array(
52
+ 'hash' => hash( 'sha256', $user_id . $username . $private_key . $salt ),
53
+ 'salt' => $salt,
54
+ );
55
+
56
+ return self::request( 'deauthenticate-user', $query, $data );
57
+ }
58
+
59
+ public static function validate( $user_id, $username, $private_key ) {
60
+ $query = array(
61
+ 'user_id' => $user_id,
62
+ 'user' => $username,
63
+ );
64
+
65
+ $salt = hash( 'sha256', uniqid( '', true ) );
66
+
67
+ $data = array(
68
+ 'hash' => hash( 'sha256', $user_id . $username . $private_key . $salt ),
69
+ 'salt' => $salt,
70
+ );
71
+
72
+ return self::request( 'validate-user', $query, $data );
73
+ }
74
+
75
+ public static function ping( $user_id, $username, $private_key ) {
76
+ $query = array(
77
+ 'user_id' => $user_id,
78
+ 'user' => $username,
79
+ );
80
+
81
+ $salt = hash( 'sha256', uniqid( '', true ) );
82
+
83
+ $data = array(
84
+ 'hash' => hash( 'sha256', $user_id . $username . $private_key . $salt ),
85
+ 'salt' => $salt,
86
+ );
87
+
88
+ return self::request( 'ping-site', $query, $data );
89
+ }
90
+
91
+ public static function send_urgent_notices( $user_id, $username, $private_key, $notices ) {
92
+ $query = array(
93
+ 'user_id' => $user_id,
94
+ 'user' => $username,
95
+ );
96
+
97
+ $salt = hash( 'sha256', uniqid( '', true ) );
98
+
99
+ $data = array(
100
+ 'hash' => hash( 'sha256', $user_id . $username . $private_key . $salt ),
101
+ 'salt' => $salt,
102
+ 'notices' => $notices,
103
+ );
104
+
105
+ return self::request( 'send-urgent-notices', $query, $data );
106
+ }
107
+
108
+ public static function request( $action, $query = array(), $data = array() ) {
109
+ if ( isset( $data['auth_token'] ) ) {
110
+ $data['iterations'] = self::$password_iterations;
111
+ }
112
+
113
+ require_once( $GLOBALS['ithemes_sync_path'] . '/functions.php' );
114
+ $default_query = array(
115
+ 'wp' => Ithemes_Sync_Functions::get_wordpress_version(),
116
+ 'site' => get_bloginfo( 'url' ),
117
+ 'timestamp' => time(),
118
+ 'auth_version' => '2',
119
+ );
120
+
121
+ if ( is_multisite() ) {
122
+ $default_query['ms'] = 1;
123
+ }
124
+
125
+ $query = array_merge( $default_query, $query );
126
+ $query['action'] = $action;
127
+
128
+ $request = '?' . http_build_query( $query, '', '&' );
129
+
130
+ $post_data = array(
131
+ 'request' => json_encode( $data ),
132
+ );
133
+
134
+ $remote_post_args = array(
135
+ 'timeout' => 30,
136
+ 'body' => $post_data,
137
+ );
138
+
139
+
140
+ $options = array(
141
+ 'use_ca_patch' => false,
142
+ 'use_ssl' => true,
143
+ );
144
+
145
+ $patch_enabled = $GLOBALS['ithemes-sync-settings']->get_option( 'use_ca_patch' );
146
+
147
+ if ( $patch_enabled ) {
148
+ $response = self::do_patched_post( $request, $remote_post_args );
149
+
150
+ if ( is_wp_error( $response ) ) {
151
+ $response = wp_remote_post( self::$secure_server_url . $request, $remote_post_args );
152
+ } else {
153
+ $options['use_ca_patch'] = true;
154
+ }
155
+ }
156
+ else {
157
+ $response = wp_remote_post( self::$secure_server_url . $request, $remote_post_args );
158
+
159
+ if ( is_wp_error( $response ) ) {
160
+ $response = self::do_patched_post( $request, $remote_post_args );
161
+
162
+ if ( ! is_wp_error( $response ) ) {
163
+ $options['use_ca_patch'] = true;
164
+ }
165
+ }
166
+ }
167
+
168
+ if ( is_wp_error( $response ) ) {
169
+ $response = wp_remote_post( self::$insecure_server_url . $request . '&insecure=1', $remote_post_args );
170
+
171
+ $options['use_ssl'] = false;
172
+ $options['use_ca_patch'] = false;
173
+ }
174
+
175
+ $GLOBALS['ithemes-sync-settings']->update_options( $options );
176
+
177
+ if ( is_wp_error( $response ) ) {
178
+ return $response;
179
+ }
180
+ if ( 200 != $response['response']['code'] ) {
181
+ return new WP_Error( 'ithemes-sync-server-failed-request', $response['response']['message'] );
182
+ }
183
+
184
+
185
+ $body = json_decode( $response['body'], true );
186
+
187
+ if ( ! is_array( $body ) ) {
188
+ return new WP_Error( 'ithemes-sync-server-unknown-response', __( 'An unrecognized server response format was received from the iThemes Sync server.', 'it-l10n-ithemes-sync' ) );
189
+ }
190
+
191
+ if ( ! empty( $body['error'] ) ) {
192
+ return new WP_Error( $body['error']['type'], $body['error']['message'] );
193
+ }
194
+
195
+
196
+ return $body;
197
+ }
198
+
199
+ private static function do_patched_post( $request, $remote_post_args ) {
200
+ self::enable_ssl_ca_patch();
201
+ $response = wp_remote_post( self::$secure_server_url . $request . '&ca_patch=1', $remote_post_args );
202
+ self::disable_ssl_ca_patch();
203
+
204
+ return $response;
205
+ }
206
+
207
+ private static function get_password_hash( $username, $password ) {
208
+ require_once( ABSPATH . 'wp-includes/class-phpass.php' );
209
+ require_once( $GLOBALS['ithemes_sync_path'] . '/class-ithemes-credentials.php' );
210
+
211
+ $password = iThemes_Credentials::get_password_hash( $username, $password );
212
+
213
+ $salted_password = $password . $username . get_bloginfo( 'url' ) . Ithemes_Sync_Functions::get_wordpress_version();
214
+ $salted_password = substr( $salted_password, 0, max( strlen( $password ), 512 ) );
215
+
216
+ $hasher = new PasswordHash( self::$password_iterations, true );
217
+ $auth_token = $hasher->HashPassword( $salted_password );
218
+
219
+ return $auth_token;
220
+ }
221
+
222
+ public static function add_ca_patch_to_curl_opts( $handle ) {
223
+ $url = curl_getinfo( $handle, CURLINFO_EFFECTIVE_URL );
224
+
225
+ if ( ! preg_match( '/^' . preg_quote( self::$secure_server_url, '/' ) . '/', $url ) ) {
226
+ return;
227
+ }
228
+
229
+ curl_setopt( $handle, CURLOPT_CAINFO, $GLOBALS['ithemes_sync_path'] . '/ca/roots.crt' );
230
+ }
231
+
232
+ public static function enable_ssl_ca_patch() {
233
+ add_action( 'http_api_curl', array( __CLASS__, 'add_ca_patch_to_curl_opts' ) );
234
+ }
235
+
236
+ public static function disable_ssl_ca_patch() {
237
+ remove_action( 'http_api_curl', array( __CLASS__, 'add_ca_patch_to_curl_opts' ) );
238
+ }
239
+ }
settings-page.php ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Code to render and manage the settings page for Project Sync.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.1
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-02 - Chris Jean
10
+ Initial version
11
+ 1.0.1 - 2013-12-02 - Chris Jean
12
+ Changed the deauthenticate() function to deauthentcate a user when the server reports that the user is not found. This prevents the issue where users are uanble to be removed.
13
+ 1.0.2 - 2014-02-13 - Chris Jean
14
+ Updated error messages for failed server connections.
15
+ Added wrap class to main settings page wrapper to allow for proper positioning of notices.
16
+ 1.1.0 - 2014-03-28 - Chris Jean
17
+ Users are now validated and shown in valid and invalid groups.
18
+ 1.1.1 - 2014-10-13 - Chris Jean
19
+ Updated Sync dashboard URL.
20
+ */
21
+
22
+
23
+ class Ithemes_Sync_Settings_Page {
24
+ private $page_name = 'ithemes-sync';
25
+
26
+ private $path_url = '';
27
+ private $self_url = '';
28
+ private $had_error = false;
29
+ private $messages = array();
30
+ private $sync_dashboard_url = 'https://sync.ithemes.com/';
31
+
32
+
33
+ public function __construct() {
34
+ require_once( $GLOBALS['ithemes_sync_path'] . '/functions.php' );
35
+
36
+ $this->path_url = Ithemes_Sync_Functions::get_url( $GLOBALS['ithemes_sync_path'] );
37
+
38
+ list( $this->self_url ) = explode( '?', $_SERVER['REQUEST_URI'] );
39
+ $this->self_url .= '?page=' . $this->page_name;
40
+
41
+ add_action( 'ithemes_sync_settings_page_index', array( $this, 'index' ) );
42
+ add_action( 'admin_print_styles', array( $this, 'add_styles' ) );
43
+ add_action( 'admin_print_scripts', array( $this, 'add_scripts' ) );
44
+ }
45
+
46
+ public function add_styles() {
47
+ wp_enqueue_style( 'ithemes-updater-settings-page-style', "{$this->path_url}/css/settings-page.css" );
48
+ }
49
+
50
+ public function add_scripts() {
51
+ $var = 'ithemes-updater-settings-page-script';
52
+
53
+ $translations = array(
54
+ 'confirm_dialog_text' => __( 'Are you sure that you wish to unsync this user?', 'it-l10n-ithemes-sync' ),
55
+ );
56
+
57
+ wp_enqueue_script( $var, "{$this->path_url}/js/settings-page.js", array( 'jquery' ) );
58
+ wp_localize_script( $var, 'ithemes_sync_settings', $translations );
59
+ }
60
+
61
+ public function index() {
62
+ $this->options = $GLOBALS['ithemes-sync-settings']->get_options();
63
+
64
+ $this->handle_post_action();
65
+
66
+ $this->show_settings();
67
+ }
68
+
69
+ private function handle_post_action() {
70
+ $post_data = Ithemes_Sync_Functions::get_post_data( array( 'username', 'password', 'action', 'user' ), true, true );
71
+ $action = $post_data['action'];
72
+
73
+ if ( 'authenticate' == $action )
74
+ $this->authenticate( $post_data );
75
+ else if ( 'deauthenticate' == $action )
76
+ $this->deauthenticate( $post_data );
77
+
78
+ $this->options = $GLOBALS['ithemes-sync-settings']->get_options();
79
+ }
80
+
81
+ private function authenticate( $data ) {
82
+ check_admin_referer( 'authenticate-user' );
83
+
84
+
85
+ require_once( $GLOBALS['ithemes_sync_path'] . '/server.php' );
86
+
87
+
88
+ $result = Ithemes_Sync_Server::authenticate( $data['username'], $data['password'] );
89
+
90
+ if ( is_wp_error( $result ) ) {
91
+ $heading = __( 'The user could not be synced.', 'it-l10n-ithemes-sync' );
92
+
93
+ $code = $result->get_error_code();
94
+
95
+ if ( 'http_request_failed' == $code )
96
+ $message = sprintf( __( '<p>The iThemes Sync server was unable to be contacted. WordPress returned the following error when trying to contact the server:</p><p>%1$s</p><p>If you continue to experience problems, please contact <a target="_blank" href="%2$s">iThemes support</a>.</p>', 'it-l10n-ithemes-sync' ), $result->get_error_message(), 'http://ithemes.com/support/' );
97
+ else if ( 'ithemes-sync-server-failed-request' == $code )
98
+ $message = sprintf( __( '<p>The iThemes Sync server was unable to process the request at this time. Please try again in a few minutes.</p><p>If you continue to experience problems, please contact <a target="_blank" href="%s">iThemes support</a>.</p>', 'it-l10n-ithemes-sync' ), 'http://ithemes.com/support/' );
99
+ else
100
+ $message = $result->get_error_message();
101
+
102
+ $this->add_error_message( $heading, $message );
103
+
104
+ return;
105
+ }
106
+
107
+ if ( empty( $result['key'] ) ) {
108
+ $heading = __( 'The user could not be synced.', 'it-l10n-ithemes-sync' );
109
+ $message = __( 'The sync request failed due to a server communication error. The server sent a response that did not include a authentication key.', 'it-l10n-ithemes-sync' );
110
+
111
+ $this->add_error_message( $heading, $message );
112
+
113
+ return;
114
+ }
115
+
116
+ if ( empty( $result['user_id'] ) ) {
117
+ $heading = __( 'The user could not be synced.', 'it-l10n-ithemes-sync' );
118
+ $message = __( 'The sync request failed due to a server communication error. The server sent a response that did not include a user ID.', 'it-l10n-ithemes-sync' );
119
+
120
+ $this->add_error_message( $heading, $message );
121
+
122
+ return;
123
+ }
124
+
125
+
126
+ $add_result = $GLOBALS['ithemes-sync-settings']->add_authentication( $result['user_id'], $data['username'], $result['key'] );
127
+
128
+ if ( is_wp_error( $add_result ) ) {
129
+ $heading = __( 'The user could not be synced.', 'it-l10n-ithemes-sync' );
130
+ $message = $add_result->get_error_message();
131
+
132
+ $this->add_error_message( $heading, $message );
133
+
134
+ return;
135
+ }
136
+
137
+
138
+ $heading = __( 'Woohoo! Your site has been synced.', 'it-l10n-ithemes-sync' );
139
+ $messages = array();
140
+
141
+ if ( 0 == $result['quota']['available'] )
142
+ $messages[] = sprintf( __( 'Your user has now used all of the sites available to be added to Sync. More information can be found on the <a href="%s" target="_blank">iThemes membership panel</a>.', 'it-l10n-ithemes-sync' ), $this->sync_dashboard_url );
143
+ else
144
+ $messages[] = sprintf( _n( 'Your user can Sync 1 additional site.', 'Your user can sync %d additional sites.', $result['quota']['available'], 'it-l10n-ithemes-sync' ), $result['quota']['available'] );
145
+
146
+ $this->add_success_message( $heading, $messages );
147
+ }
148
+
149
+ private function deauthenticate( $data ) {
150
+ require_once( $GLOBALS['ithemes_sync_path'] . '/server.php' );
151
+
152
+
153
+ $options = $GLOBALS['ithemes-sync-settings']->get_options();
154
+ $user_details = $GLOBALS['ithemes-sync-settings']->get_authentication_details( $data['user'] );
155
+
156
+
157
+ $result = Ithemes_Sync_Server::deauthenticate( $data['user'], $user_details['username'], $user_details['key'] );
158
+
159
+ if ( is_wp_error( $result ) && ( 'authentication' != $result->get_error_code() ) ) {
160
+ $heading = __( 'The user could not be unsynced.', 'it-l10n-ithemes-sync' );
161
+ $message = $result->get_error_message();
162
+
163
+ $this->add_error_message( $heading, $message );
164
+
165
+ return;
166
+ }
167
+
168
+
169
+ $result = $GLOBALS['ithemes-sync-settings']->remove_authentication( $data['user'], $user_details['username'] );
170
+
171
+ if ( is_wp_error( $result ) ) {
172
+ $heading = __( 'The user could not be unsynced.', 'it-l10n-ithemes-sync' );
173
+ $message = $result->get_error_message();
174
+
175
+ $this->add_error_message( $heading, $message );
176
+
177
+ return;
178
+ }
179
+
180
+
181
+ $heading = __( 'The user was successfully unsynced.', 'it-l10n-ithemes-sync' );
182
+
183
+ $this->add_success_message( $heading );
184
+ }
185
+
186
+ private function show_messages() {
187
+ foreach ( $this->messages as $class => $messages ) {
188
+ foreach ( $messages as $message )
189
+ $this->show_message( $message['heading'], $message['messages'], $class );
190
+ }
191
+ }
192
+
193
+ private function show_message( $heading, $messages, $class ) {
194
+
195
+ ?>
196
+ <div class="message <?php echo $class; ?>">
197
+ <h3><?php echo $heading; ?></h3>
198
+
199
+ <?php foreach ( (array) $messages as $message ) : ?>
200
+ <p><?php echo $message; ?></p>
201
+ <?php endforeach; ?>
202
+ </div>
203
+ <?php
204
+
205
+ }
206
+
207
+ private function add_message( $heading, $messages, $class ) {
208
+ $this->messages[$class][] = compact( 'heading', 'messages' );
209
+ }
210
+
211
+ private function add_success_message( $heading, $messages = array() ) {
212
+ $this->add_message( $heading, $messages, 'success' );
213
+ }
214
+
215
+ private function add_error_message( $heading, $messages = array() ) {
216
+ $this->add_message( $heading, $messages, 'error' );
217
+ $this->had_error = true;
218
+ }
219
+
220
+ private function save_settings() {
221
+ check_admin_referer( 'save_settings', 'ithemes_sync_nonce' );
222
+
223
+
224
+ $settings_defaults = array();
225
+
226
+
227
+ $settings = array();
228
+
229
+ foreach ( $settings_defaults as $var => $val ) {
230
+ if ( isset( $_POST[$var] ) )
231
+ $settings[$var] = $_POST[$var];
232
+ else
233
+ $settings[$var] = $val;
234
+ }
235
+
236
+
237
+ $GLOBALS['ithemes-sync-settings']->update_options( $settings );
238
+
239
+ $this->messages[] = __( 'Settings saved', 'it-l10n-ithemes-sync' );
240
+ }
241
+
242
+ public function show_settings() {
243
+ $post_data = Ithemes_Sync_Functions::get_post_data( array( 'username', 'password' ), true );
244
+
245
+ if ( ! is_multisite() ) {
246
+ $validations = $GLOBALS['ithemes-sync-settings']->validate_authentications();
247
+ }
248
+
249
+ $valid_users = array();
250
+ $invalid_users = array();
251
+
252
+ uksort( $this->options['authentications'], array( $this, 'sort_usernames' ) );
253
+
254
+ foreach ( array_keys( $this->options['authentications'] ) as $user_id ) {
255
+ if ( ! isset( $validations ) || $validations[$user_id] ) {
256
+ $valid_users[] = $user_id;
257
+ } else {
258
+ $invalid_users[] = $user_id;
259
+ }
260
+ }
261
+
262
+
263
+ ?>
264
+ <div class="ithemes-sync-wrapper wrap">
265
+ <?php if ( empty( $this->options['authentications'] ) ) : ?>
266
+ <h2><?php _e( 'Sync This Site', 'it-l10n-ithemes-sync' ); ?></h2>
267
+ <?php else : ?>
268
+ <h2><?php _e( 'iThemes Sync', 'it-l10n-ithemes-sync' ); ?></h2>
269
+ <?php endif; ?>
270
+
271
+
272
+ <?php $this->show_messages(); ?>
273
+
274
+
275
+ <?php if ( ! empty( $this->options['authentications'] ) ) : ?>
276
+ <a class="ithemes-sync-button" href="<?php echo $this->sync_dashboard_url; ?>" target="_blank"><?php _e( 'Go Manage Your Synced Sites', 'it-l10n-ithemes-sync' ); ?></a>
277
+
278
+ <div class="ithemes-sync-section ithemes-sync-manage-users">
279
+ <h3><?php _e( 'Manage Synced Users', 'it-l10n-ithemes-sync' ); ?></h3>
280
+
281
+ <p><?php _e( 'Sync allows you to sync your site with multiple users.<br>View the list of synced users below, unsync users if needed, or add additional users below.', 'it-l10n-ithemes-sync' ); ?></p>
282
+
283
+ <?php if ( ! empty( $valid_users ) ) : ?>
284
+ <div class="ithemes-sync-users ithemes-sync-valid-users">
285
+ <h4><?php _e( 'Synced Users', 'it-l10n-ithemes-sync' ); ?></h4>
286
+
287
+ <ul>
288
+ <?php foreach ( $valid_users as $user_id ) : ?>
289
+ <li>
290
+ <div class="user"><?php echo $this->options['authentications'][$user_id]['username']; ?></div>
291
+ <div class="deauthenticate"><a href="<?php echo "{$this->self_url}&action=deauthenticate&user=$user_id"; ?>">Unsync</a>
292
+ </li>
293
+ <?php endforeach; ?>
294
+ </ul>
295
+ </div>
296
+ <?php endif; ?>
297
+
298
+ <?php if ( ! empty( $invalid_users ) ) : ?>
299
+ <div class="ithemes-sync-users ithemes-sync-invalid-users">
300
+ <h4><?php _e( 'Invalid Users', 'it-l10n-ithemes-sync' ); ?></h4>
301
+
302
+ <p><?php _e( 'The following users were not recognized by the server. Unsync them and resync them again to fix this error.', 'it-l10n-ithemes-sync' ); ?></p>
303
+
304
+ <ul>
305
+ <?php foreach ( $invalid_users as $user_id ) : ?>
306
+ <li>
307
+ <div class="user"><?php echo $this->options['authentications'][$user_id]['username']; ?></div>
308
+ <div class="deauthenticate"><a href="<?php echo "{$this->self_url}&action=deauthenticate&user=$user_id"; ?>">Unsync</a>
309
+ </li>
310
+ <?php endforeach; ?>
311
+ </ul>
312
+ </div>
313
+ <?php endif; ?>
314
+ </div>
315
+ <?php endif; ?>
316
+
317
+
318
+ <div class="ithemes-sync-section ithemes-sync-authorize">
319
+ <?php if ( empty( $this->options['authentications'] ) ) : ?>
320
+ <p><?php _e( 'Enter your iThemes username & password to sync this site.', 'it-l10n-ithemes-sync' ); ?></p>
321
+ <?php else : ?>
322
+ <h3><?php _e( 'Add Users', 'it-l10n-ithemes-sync' ); ?></h3>
323
+ <p><?php _e( 'Add additional users if more than one person will be managing updates for this site.<br>To sync an additional user, enter their iThemes username and password below.', 'it-l10n-ithemes-sync' ); ?></p>
324
+ <?php endif; ?>
325
+
326
+ <form id="ithemes-sync-authenticate" enctype="multipart/form-data" method="post" action="<?php echo $this->self_url; ?>">
327
+ <label for="username"><?php _e( 'Username', 'it-l10n-ithemes-sync' ); ?></label><br>
328
+ <input type="text" id="username" name="username" value="<?php if ( $this->had_error ) echo esc_attr( $post_data['username'] ); ?>">
329
+
330
+ <label for="password"><?php _e( 'Password', 'it-l10n-ithemes-sync' ); ?></label><br>
331
+ <input type="password" id="password" name="password" value="<?php if ( $this->had_error ) echo esc_attr( $post_data['password'] ); ?>">
332
+
333
+ <input type="submit" id="submit" value="<?php _e( 'Sync', 'it-l10n-ithemes-sync' ); ?>">
334
+ <input type="hidden" name="action" value="authenticate">
335
+
336
+ <?php wp_nonce_field( 'authenticate-user' ); ?>
337
+ </form>
338
+ </div>
339
+ </div>
340
+ <?php
341
+
342
+ }
343
+
344
+ private function sort_usernames( $a, $b ) {
345
+ return strcasecmp( $this->options['authentications'][$a]['username'], $this->options['authentications'][$b]['username'] );
346
+ }
347
+ }
348
+
349
+ new Ithemes_Sync_Settings_Page();
settings.php ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Central management of options storage for Project Sync.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.0.1 - 2013-11-18 - Chris Jean
12
+ Updated brace format.
13
+ 1.1.0 - 2013-11-19 - Chris Jean
14
+ Added the show_sync option.
15
+ 1.2.0 - 2014-03-20 - Chris Jean
16
+ Added validate_authentications(), validate_authentication(), and do_ping_check().
17
+ */
18
+
19
+
20
+ class Ithemes_Sync_Settings {
21
+ private $option_name = 'ithemes-sync-cache';
22
+
23
+ private $options = false;
24
+ private $options_modified = false;
25
+ private $initialized = false;
26
+
27
+ private $default_options = array(
28
+ 'authentications' => array(),
29
+ 'use_ca_patch' => false,
30
+ 'show_sync' => true,
31
+ );
32
+
33
+
34
+ public function __construct() {
35
+ $GLOBALS['ithemes-sync-settings'] = $this;
36
+
37
+ add_action( 'shutdown', array( $this, 'shutdown' ) );
38
+ }
39
+
40
+ public function init() {
41
+ if ( $this->initialized ) {
42
+ return;
43
+ }
44
+
45
+ $this->initialized = true;
46
+
47
+ $this->load();
48
+ }
49
+
50
+ public function load() {
51
+ if ( false !== $this->options )
52
+ return;
53
+
54
+ $this->options = get_site_option( $this->option_name, false );
55
+
56
+ if ( ( false === $this->options ) || ! is_array( $this->options ) ) {
57
+ $this->options = array();
58
+ }
59
+
60
+ $this->options = array_merge( $this->default_options, $this->options );
61
+ }
62
+
63
+ public function shutdown() {
64
+ if ( $this->options_modified ) {
65
+ update_site_option( $this->option_name, $this->options );
66
+ }
67
+ }
68
+
69
+ public function get_options() {
70
+ $this->init();
71
+
72
+ return $this->options;
73
+ }
74
+
75
+ public function get_option( $var ) {
76
+ $this->init();
77
+
78
+ if ( isset( $this->options[$var] ) ) {
79
+ return $this->options[$var];
80
+ }
81
+
82
+ return null;
83
+ }
84
+
85
+ public function update_options( $updates ) {
86
+ $this->init();
87
+
88
+ $this->options = array_merge( $this->options, $updates );
89
+ $this->options_modified = true;
90
+ }
91
+
92
+ public function add_authentication( $user_id, $username, $key ) {
93
+ $this->init();
94
+
95
+
96
+ if ( ! isset( $this->options['authentications'] ) || ! is_array( $this->options['authentications'] ) ) {
97
+ $this->options['authentications'] = array();
98
+ }
99
+
100
+ $local_user = wp_get_current_user();
101
+
102
+ $this->options['authentications'][$user_id] = array(
103
+ 'key' => $key,
104
+ 'timestamp' => time(),
105
+ 'local_user' => $local_user->user_login,
106
+ 'username' => $username,
107
+ );
108
+
109
+ $this->options_modified = true;
110
+
111
+
112
+ if ( ! empty( $this->options['authentications'] ) ) {
113
+ update_site_option( 'ithemes-sync-authenticated', true );
114
+ } else {
115
+ delete_site_option( 'ithemes_sync_hide_authenticate_notice' );
116
+ }
117
+
118
+
119
+ return true;
120
+ }
121
+
122
+ public function remove_authentication( $user_id, $username ) {
123
+ $this->init();
124
+
125
+
126
+ if ( ! isset( $this->options['authentications'] ) || ! is_array( $this->options['authentications'] ) ) {
127
+ $this->options['authentications'] = array();
128
+ }
129
+
130
+ if ( ! isset( $this->options['authentications'][$user_id] ) ) {
131
+ return new WP_Error( 'unauthenticated-user', __( 'The user is not authenticated. Could not remove authentication for the user.', 'it-l10n-ithemes-sync' ) );
132
+ }
133
+
134
+
135
+ unset( $this->options['authentications'][$user_id] );
136
+
137
+ $this->options_modified = true;
138
+
139
+
140
+ if ( empty( $this->options['authentications'] ) ) {
141
+ delete_site_option( 'ithemes-sync-authenticated' );
142
+ delete_site_option( 'ithemes_sync_hide_authenticate_notice' );
143
+ }
144
+
145
+
146
+ return true;
147
+ }
148
+
149
+ public function get_authentication_details( $user_id ) {
150
+ if ( ! isset( $this->options['authentications'][$user_id] ) ) {
151
+ return false;
152
+ }
153
+
154
+ return $this->options['authentications'][$user_id];
155
+ }
156
+
157
+ public function validate_authentications() {
158
+ $validations = array();
159
+
160
+ foreach ( $this->options['authentications'] as $user_id => $details ) {
161
+ $validations[$user_id] = $this->validate_authentication( $user_id );
162
+ }
163
+
164
+ return $validations;
165
+ }
166
+
167
+ public function validate_authentication( $user_id ) {
168
+ require_once( $GLOBALS['ithemes_sync_path'] . '/server.php' );
169
+
170
+
171
+ $authentication = $this->get_authentication_details( $user_id );
172
+
173
+ if ( empty( $authentication ) ) {
174
+ return false;
175
+ }
176
+
177
+ $result = Ithemes_Sync_Server::validate( $user_id, $authentication['username'], $authentication['key'] );
178
+
179
+ if ( is_wp_error( $result ) || ! is_array( $result ) || ! isset( $result['success'] ) || ! $result['success'] ) {
180
+ return false;
181
+ }
182
+
183
+ return true;
184
+ }
185
+
186
+ public function do_ping_check( $user_id = false ) {
187
+ require_once( $GLOBALS['ithemes_sync_path'] . '/server.php' );
188
+
189
+
190
+ if ( empty( $user_id ) ) {
191
+ $user_id = current( array_keys( $this->options['authentications'] ) );
192
+ }
193
+
194
+ $authentication = $this->get_authentication_details( $user_id );
195
+
196
+ if ( empty( $authentication ) ) {
197
+ return new WP_Error( 'ithemes-sync-invalid-user', __( 'A valid user was unable to be found. A valid user is required in order to do a successful ping.', 'it-l10n-ithemes-sync' ) );
198
+ }
199
+
200
+
201
+ $result = Ithemes_Sync_Server::validate( $user_id, $authentication['username'], $authentication['key'] );
202
+
203
+ if ( is_wp_error( $result ) || ! is_array( $result ) || ! isset( $result['success'] ) || ! $result['success'] ) {
204
+ return false;
205
+ }
206
+
207
+ return true;
208
+ }
209
+ }
210
+
211
+ new Ithemes_Sync_Settings();
social.php ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Ithemes_Sync_Social {
4
+
5
+ private $_page_name = 'sync-social';
6
+
7
+ /**
8
+ * Array containing all accounts linked with this site
9
+ *
10
+ * @var array - Index is account type (ie "twitter"), value is array of accounts
11
+ */
12
+ private $_linked_accounts = array();
13
+
14
+ /**
15
+ * Array containing accounts that are not used by default.
16
+ *
17
+ * @var array - Index is account type (ie "twitter"), value is array of accounts
18
+ */
19
+ private $_non_default_accounts = array();
20
+
21
+ /**
22
+ * Array containing the default content for each account type
23
+ *
24
+ * @var array - Index is account type (ie "twitter"), value is the default content string
25
+ */
26
+ private $_default_content = array();
27
+
28
+ public function __construct() {
29
+ if ( $this->_have_linked_accounts() ) {
30
+ add_action( 'init', array( $this, 'init' ) );
31
+ }
32
+ }
33
+
34
+ public function init() {
35
+ add_action( 'post_submitbox_misc_actions', array( $this, 'post_submitbox_misc_actions' ) );
36
+ add_action( 'admin_enqueue_scripts', array( $this, 'metabox_assets' ) );
37
+ add_action( 'transition_post_status', array( $this, 'transition_post_status' ), null, 3 );
38
+ }
39
+
40
+ public function metabox_assets( $hook ) {
41
+ if ( $hook != 'post.php' && $hook != 'post-new.php' )
42
+ return;
43
+
44
+ wp_enqueue_script( 'ithemes-social-submitbox', plugins_url( 'js/social-metabox.js', __FILE__ ), array('jquery') );
45
+ wp_enqueue_style( 'ithemes-social-submitbox', plugins_url( 'css/social-metabox.css', __FILE__ ) );
46
+ }
47
+
48
+ private function _get_linked_accounts( $nocache = false ) {
49
+ if ( empty( $this->_linked_accounts ) || $nocache ) {
50
+ $this->_linked_accounts['twitter'] = get_option( 'ithemes-sync-twitter-accounts', array() );
51
+ }
52
+ return $this->_linked_accounts;
53
+ }
54
+
55
+ private function _get_non_default_accounts( $nocache = false ) {
56
+ if ( empty( $this->_non_default_accounts ) || $nocache ) {
57
+ $this->_non_default_accounts['twitter'] = get_option( 'ithemes-sync-twitter-non-default-accounts', array() );
58
+ }
59
+ return $this->_non_default_accounts;
60
+ }
61
+
62
+ private function _get_default_content( $nocache = false ) {
63
+ if ( empty( $this->_default_content ) || $nocache ) {
64
+ $this->_default_content['twitter'] = get_option( 'ithemes-sync-twitter-default-content' );
65
+ if ( false === $this->_default_content['twitter'] ) {
66
+ $this->_default_content['twitter'] = '{post_title} - {post_url}';
67
+ }
68
+ }
69
+ return $this->_default_content;
70
+ }
71
+
72
+ private function _have_linked_accounts() {
73
+ foreach ( $this->_get_linked_accounts() as $type => $accounts ) {
74
+ if ( ! empty( $accounts ) ) {
75
+ return true;
76
+ }
77
+ }
78
+ return false;
79
+ }
80
+
81
+ public function post_submitbox_misc_actions() {
82
+ $post = get_post();
83
+ ?>
84
+ <div id="ithemes-social" class="misc-pub-section">
85
+ <div class="ithemes-social-header">
86
+ <h3><?php _e('Social', 'it-l10n-ithemes-sync'); ?></h3>
87
+ </div>
88
+ <?php
89
+ if ( 'publish' === $post->post_status ) {
90
+ $this->_post_submitbox_content_published();
91
+ } else {
92
+ $this->_post_submitbox_content_unpublished();
93
+ }
94
+ ?>
95
+ </div>
96
+
97
+ <?php
98
+ }
99
+
100
+ private function _post_submitbox_content_published() {
101
+ ?>
102
+ <p class="it-social-notice"><?php _e('This post is already published.', 'it-l10n-ithemes-sync'); ?></p>
103
+ <?php
104
+ }
105
+
106
+ private function _post_submitbox_content_unpublished() {
107
+ $accounts = $this->_get_linked_accounts();
108
+ $not_default = $this->_get_non_default_accounts();
109
+ $default_content = $this->_get_default_content();
110
+ ?>
111
+ <ul class="ithemes-social-accounts">
112
+ <?php
113
+ foreach ( $accounts['twitter'] as $twitter_id => $twitter_name ) {
114
+ $checked = in_array( $twitter_id, $not_default['twitter'] )? '':' checked';
115
+ ?>
116
+ <li class="test">
117
+ <input type="checkbox" name="ithemes-social[twitter][<?php echo esc_attr( $twitter_id ) ?>][send]" id="ithemes-social-twitter-<?php echo esc_attr( $twitter_id ) ?>" value="true"<?php echo $checked; ?>>
118
+ <label for="ithemes-social-twitter-<?php echo esc_attr( $twitter_id ) ?>">Twitter: <em>@<?php echo esc_html( $twitter_name ) ?></em></label>
119
+ <a class="ithemes-social-edit-content"><?php _e('Edit', 'it-l10n-ithemes-sync'); ?></a>
120
+ <div class="ithemes-social-account-content hidden">
121
+ <textarea class="widefat" name="ithemes-social[twitter][<?php echo esc_attr( $twitter_id ) ?>][content]"><?php echo esc_textarea( $default_content['twitter'] ); ?></textarea>
122
+ <div class="ithemes-social-template-tags">
123
+ <?php _e('Template Tags', 'it-l10n-ithemes-sync'); ?>: <br />
124
+ {post_title} {post_url} {post_author}
125
+ </div>
126
+ <div class="ithemes-social-char-count"></div>
127
+ </div>
128
+ </li>
129
+ <?php
130
+ }
131
+ ?>
132
+ </ul>
133
+ <?php
134
+ }
135
+
136
+ public function transition_post_status( $new_status, $old_status, $post ) {
137
+ if ( $old_status != 'publish' && $new_status == 'publish' && ! empty( $_POST['ithemes-social'] ) ) {
138
+ /**
139
+ * @todo Allow custom tweet content
140
+ * @todo use send-to to say which accounts should be tweeted to
141
+ */
142
+ $post_title = get_the_title( $post->ID );
143
+ $data = array(
144
+ 'post' => $post,
145
+ 'twitter' => array(),
146
+ );
147
+ if ( ! empty( $_POST['ithemes-social']['twitter'] ) )
148
+ foreach ( $_POST['ithemes-social']['twitter'] as $twitter_id => $twitter ) {
149
+ if ( ! empty( $twitter['send'] ) && 'true' === $twitter['send'] && ! empty( $twitter['content'] ) ) {
150
+ $data['twitter'][$twitter_id] = $this->_handle_template_tags( stripslashes( $twitter['content'] ), $post );
151
+ }
152
+ }
153
+ $result = ithemes_sync_send_urgent_notice( 'ithemes-sync', 'social', 'Post published', "'$post_title' has been published", $data );
154
+ }
155
+ }
156
+
157
+ private function _handle_template_tags( $content, $post ) {
158
+ $tags = array(
159
+ '{post_title}' => $post->post_title,
160
+ '{post_url}' => get_permalink( $post->ID ),
161
+ '{post_author}' => get_the_author_meta( 'display_name', $post->post_author ),
162
+ );
163
+
164
+ return str_replace( array_keys( $tags ), $tags, $content );
165
+ }
166
+ }
167
+ new Ithemes_Sync_Social();
uninstall.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Handles uninstalling the plugin so that options can be cleaned up.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-06 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) )
15
+ exit();
16
+
17
+
18
+ delete_site_option( 'ithemes-sync-cache' );
19
+ delete_site_option( 'ithemes-sync-authenticated' );
20
+ delete_site_option( 'ithemes_sync_hide_authenticate_notice' );
21
+
22
+ delete_site_transient( 'ithemes-sync-activated' );
upgrader-skin.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Upgrader skin for preventing output when upgrades are occurring.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-05 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Upgrader_Skin extends Bulk_Upgrader_Skin {
15
+ function header() {}
16
+ function footer() {}
17
+ function bulk_header() {}
18
+ function bulk_footer() {}
19
+ function before( $title = '' ) {}
20
+ function after( $title = '' ) {}
21
+ function error( $errors ) {}
22
+ function feedback( $string ) {}
23
+ }
verbs/db-optimization.php ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the db-optimization verb.
5
+ Written by Glenn Ansley for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2016-11-17 - Glenn Ansley
10
+ */
11
+
12
+
13
+ class Ithemes_Sync_Verb_DB_Optimization extends Ithemes_Sync_Verb {
14
+ public static $name = 'db-optimization';
15
+ public static $description = 'Optimizes the database.';
16
+ public static $status_element_name = 'optimize';
17
+ public static $show_in_status_by_default = false;
18
+
19
+ private $date_format = 'Y-m-d';
20
+ private $optimize_options = array();
21
+ private $default_arguments = array(
22
+ 'args' => array(
23
+ 'action' => 'get-available',
24
+ 'selected' => array(),
25
+ ),
26
+ );
27
+ private $registered_optimizations = array(
28
+ 'delete-trackback-urls' => 'delete_trackback_urls',
29
+ 'delete-all-revisions' => 'delete_all_revisions',
30
+ 'delete-all-trackbacks' => 'delete_all_trackbacks',
31
+ 'delete-all-pingbacks' => 'delete_all_pingbacks',
32
+ 'delete-all-autodrafts' => 'delete_all_autodrafts',
33
+ 'delete-all-trashed-items' => 'delete_all_trashed_items',
34
+ 'delete-unapproved-comments' => 'delete_unapproved_comments',
35
+ 'delete-trashed-comments' => 'delete_trashed_comments',
36
+ 'delete-spam-comments' => 'delete_spam_comments',
37
+ 'delete-orphaned-commentmeta' => 'delete_orphaned_commentmeta',
38
+ 'delete-akismet-metadata' => 'delete_akismet_metadata',
39
+ 'delete-expired-transients' => 'delete_expired_transients',
40
+ );
41
+
42
+ public function run( $arguments ) {
43
+ $this->arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
44
+ $this->date_format = get_option( 'date_format' );
45
+ $this->optimize_options = get_option( 'ithemes_sync_optimization' );
46
+
47
+ $actions = array(
48
+ 'get-available' => 'get_available',
49
+ 'run' => 'run_optimization',
50
+ );
51
+
52
+ if ( ! is_callable( array( $GLOBALS['ithemes-sync-api'], 'run' ) ) ) {
53
+ return new WP_Error( 'missing-method-api-run', 'The Ithemes_Sync_API::run function is not callable. Unable to generate status details.' );
54
+ }
55
+
56
+ if ( empty( $arguments['args']['action'] ) || empty( $actions[$arguments['args']['action']] ) || ! is_callable( array( $this, $actions[$arguments['args']['action']] ) ) ) {
57
+ return new WP_Error( "missing-function-" . $actions[$arguments['args']['action']], "Due to an unknown issue, the " . $actions[$arguments['args']['action']] . " function is not available." );
58
+ } else {
59
+ return call_user_func( array( $this, $actions[$arguments['args']['action']] ) );
60
+ }
61
+ return array();
62
+ }
63
+
64
+ private function get_available() {
65
+ $optimizations = array();
66
+ foreach ( $this->registered_optimizations as $key => $function ) {
67
+ if ( is_callable( array( $this, $function ) ) && false !== ( $result = $this->$function( 'is_available' ) ) ) {
68
+ $optimizations[$key] = $result;
69
+ }
70
+ }
71
+ return array('success' => 1, 'optimizations' => $optimizations);
72
+ }
73
+
74
+ private function run_optimization() {
75
+ $optimized = array();
76
+ if ( empty( $this->arguments['args']['selected'] ) ) {
77
+ return array('success' => 0);
78
+ }
79
+ foreach( (array) $this->arguments['args']['selected'] as $selected ) {
80
+ $function = empty( $this->registered_optimizations[$selected] ) ? false : $this->registered_optimizations[$selected];
81
+ if ( ! empty( $function ) && is_callable( array( $this, $function ) ) && false !== ( $result = $this->$function( 'run' ) ) ) {
82
+ $optimized[$key] = $result;
83
+ }
84
+ }
85
+ update_option( 'ithemes_sync_optimization', $this->optimize_options );
86
+ return $this->get_available();
87
+ }
88
+
89
+ private function delete_trackback_urls( $action ) {
90
+ global $wpdb;
91
+ switch( $action ) {
92
+ case 'is_available' :
93
+ $applies = $wpdb->get_var( "SELECT COUNT(ID) FROM $wpdb->posts WHERE pinged !='' OR to_ping != ''" );
94
+
95
+ return array(
96
+ 'title' => __( 'Delete Trackback URLs' ),
97
+ 'description' => esc_attr__( 'Deletes URLs added to the "pinged" and "to_ping" columns in your posts table.', 'it-l10n-ithemes-sync' ),
98
+ 'action' => 'delete-trackback-urls',
99
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
100
+ 'has_items' => ! empty( $applies ),
101
+ 'last_run' => $this->get_last_run( 'delete-trackback-urls' ),
102
+ );
103
+ break;
104
+ case 'run' :
105
+ $this->update_last_run_var( 'delete-trackback-urls' );
106
+ if ( $result = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE pinged !='' OR to_ping != ''" ) ) {
107
+ if ( $wpdb->query( "UPDATE $wpdb->posts SET pinged = '', to_ping = '' WHERE ID IN (" . implode( $result, ',' ) . ")" ) ) {
108
+ return true;
109
+ }
110
+ }
111
+ break;
112
+ }
113
+ return false;
114
+ }
115
+
116
+ private function delete_all_revisions( $action ) {
117
+ global $wpdb;
118
+ switch( $action ) {
119
+ case 'is_available' :
120
+ $applies = $wpdb->get_var( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_type = 'revision';" );
121
+ return array(
122
+ 'title' => 'Delete All Revisions',
123
+ 'action' => 'delete-all-revisions',
124
+ 'description' => 'Deletes all revisions from posts, pages, and all other post-types.',
125
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
126
+ 'has_items' => ! empty( $applies ),
127
+ 'last_run' => $this->get_last_run( 'delete-all-revisions' ),
128
+ );
129
+ case 'run' :
130
+ $this->update_last_run_var( 'delete-all-revisions' );
131
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->posts WHERE post_type = 'revision'" ) ) {
132
+ return true;
133
+ }
134
+ break;
135
+ }
136
+ return false;
137
+ }
138
+
139
+ private function delete_all_trackbacks( $action ) {
140
+ global $wpdb;
141
+ switch( $action ) {
142
+ case 'is_available' :
143
+ $applies = $wpdb->get_var( "SELECT COUNT(comment_ID) FROM $wpdb->comments WHERE comment_type = 'trackback';" );
144
+ return array(
145
+ 'title' => 'Delete All Trackbacks',
146
+ 'action' => 'delete-all-trackbacks',
147
+ 'description' => 'Deletes all trackbacks from other sites stored in your comments table.',
148
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
149
+ 'has_items' => ! empty( $applies ),
150
+ 'last_run' => $this->get_last_run( 'delete-all-trackbacks' ),
151
+ );
152
+ case 'run' :
153
+ $this->update_last_run_var( 'delete-all-trackbacks' );
154
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->comments WHERE comment_type = 'trackback'" ) ) {
155
+ return true;
156
+ }
157
+ break;
158
+ }
159
+ return false;
160
+ }
161
+
162
+ private function delete_all_pingbacks( $action ) {
163
+ global $wpdb;
164
+ switch( $action ) {
165
+ case 'is_available' :
166
+ $applies = $wpdb->get_var( "SELECT COUNT(comment_ID) FROM $wpdb->comments WHERE comment_type = 'pingback';" );
167
+ return array(
168
+ 'title' => 'Delete All Pingbacks',
169
+ 'action' => 'delete-all-pingbacks',
170
+ 'description' => 'Deletes all pingbacks from other sites stored in your comments table.',
171
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
172
+ 'has_items' => ! empty( $applies ),
173
+ 'last_run' => $this->get_last_run( 'delete-all-pingbacks' ),
174
+ );
175
+ case 'run' :
176
+ $this->update_last_run_var( 'delete-all-pingbacks' );
177
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->comments WHERE comment_type = 'pingback'" ) ) {
178
+ return true;
179
+ }
180
+ break;
181
+ }
182
+ return false;
183
+
184
+ }
185
+
186
+ private function delete_all_autodrafts( $action ) {
187
+ global $wpdb;
188
+ switch( $action ) {
189
+ case 'is_available' :
190
+ $applies = $wpdb->get_var( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_status = 'auto-draft';" );
191
+ return array(
192
+ 'title' => 'Delete All Auto-Drafts',
193
+ 'action' => 'delete-all-autodrafts',
194
+ 'description' => 'Deletes all auto-drats from posts, pages, and all other post-types.',
195
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
196
+ 'has_items' => ! empty( $applies ),
197
+ 'last_run' => $this->get_last_run( 'delete-all-autodrafts' ),
198
+ );
199
+ case 'run' :
200
+ $this->update_last_run_var( 'delete-all-autodrafts' );
201
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->posts WHERE post_status = 'auto-draft'" ) ) {
202
+ return true;
203
+ }
204
+ break;
205
+ }
206
+ return false;
207
+
208
+ }
209
+
210
+ private function delete_all_trashed_items( $action ) {
211
+ global $wpdb;
212
+ switch( $action ) {
213
+ case 'is_available' :
214
+ $applies = $wpdb->get_var( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_status = 'trash';" );
215
+ return array(
216
+ 'title' => 'Delete All Trashed Items',
217
+ 'action' => 'delete-all-trashed-items',
218
+ 'description' => 'Deletes all posts, pages, menus, and all other post-types that have been trashed but not yet deleted.',
219
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
220
+ 'has_items' => ! empty( $applies ),
221
+ 'last_run' => $this->get_last_run( 'delete-all-trashed-items' ),
222
+ );
223
+ case 'run' :
224
+ $this->update_last_run_var( 'delete-all-trashed-items' );
225
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->posts WHERE post_status = 'trash'" ) ) {
226
+ return true;
227
+ }
228
+ break;
229
+ }
230
+ return false;
231
+
232
+ }
233
+
234
+ private function delete_unapproved_comments( $action ) {
235
+ global $wpdb;
236
+ switch( $action ) {
237
+ case 'is_available' :
238
+ $applies = $wpdb->get_var( "SELECT COUNT(comment_ID) FROM $wpdb->comments WHERE comment_approved = '0';" );
239
+ return array(
240
+ 'title' => 'Delete Unapproved Comments',
241
+ 'action' => 'delete-unapproved-comments',
242
+ 'description' => 'Deletes all comments waiting to be approved.',
243
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
244
+ 'has_items' => ! empty( $applies ),
245
+ 'last_run' => $this->get_last_run( 'delete-unapproved-comments' ),
246
+ );
247
+ case 'run' :
248
+ $this->update_last_run_var( 'delete-unapproved-comments' );
249
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->comments WHERE comment_approved = '0'" ) ) {
250
+ return true;
251
+ }
252
+ break;
253
+ }
254
+ return false;
255
+
256
+ }
257
+
258
+ private function delete_trashed_comments( $action ) {
259
+ global $wpdb;
260
+ switch( $action ) {
261
+ case 'is_available' :
262
+ $applies = $wpdb->get_var( "SELECT COUNT(comment_ID) FROM $wpdb->comments WHERE comment_approved = 'trash';" );
263
+ return array(
264
+ 'title' => 'Delete Trashed Comments',
265
+ 'action' => 'delete-trashed-comments',
266
+ 'description' => 'Deletes all trashed comments that have yet to be deleted.',
267
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
268
+ 'has_items' => ! empty( $applies ),
269
+ 'last_run' => $this->get_last_run( 'delete-trashed-comments' ),
270
+ );
271
+ case 'run' :
272
+ $this->update_last_run_var( 'delete-trashed-comments' );
273
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->comments WHERE comment_approved = 'trash'" ) ) {
274
+ return true;
275
+ }
276
+ break;
277
+ }
278
+ return false;
279
+ }
280
+
281
+ private function delete_spam_comments( $action ) {
282
+ global $wpdb;
283
+ switch( $action ) {
284
+ case 'is_available' :
285
+ $applies = $wpdb->get_var( "SELECT COUNT(comment_id) FROM $wpdb->comments WHERE comment_approved = 'spam';" );
286
+ return array(
287
+ 'title' => 'Delete Spam Comments',
288
+ 'action' => 'delete-spam-comments',
289
+ 'description' => 'Deletes all comments that have been marked as spam.',
290
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
291
+ 'has_items' => ! empty( $applies ),
292
+ 'last_run' => $this->get_last_run( 'delete-spam-comments' ),
293
+ );
294
+ case 'run' :
295
+ $this->update_last_run_var( 'delete-spam-comments' );
296
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->comments WHERE comment_approved = 'spam'" ) ) {
297
+ return true;
298
+ }
299
+ break;
300
+ }
301
+ return false;
302
+ }
303
+
304
+ private function delete_orphaned_commentmeta( $action ) {
305
+ global $wpdb;
306
+ switch( $action ) {
307
+ case 'is_available' :
308
+ $applies = $wpdb->get_var( "SELECT COUNT(comment_id) FROM $wpdb->commentmeta WHERE comment_id NOT IN (SELECT comment_ID FROM $wpdb->comments);" );
309
+ return array(
310
+ 'title' => 'Delete Orphaned Commentmeta',
311
+ 'action' => 'delete-orphaned-commentmeta',
312
+ 'description' => 'Deletes all commentmeta that references a deleted comment.',
313
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
314
+ 'has_items' => ! empty( $applies ),
315
+ 'last_run' => $this->get_last_run( 'delete-orphaned-commentmeta' ),
316
+ );
317
+ case 'run' :
318
+ $this->update_last_run_var( 'delete-orphaned-commentmeta' );
319
+ if ( $result = $wpdb->get_col( "SELECT meta_id FROM $wpdb->commentmeta WHERE comment_id NOT IN (SELECT comment_ID FROM $wpdb->comments);" ) ) {
320
+ if ( $wpdb->query( "DELETE FROM $wpdb->commentmeta WHERE meta_id IN (" . implode( $result, ',' ) . ")" ) ) {
321
+ return true;
322
+ }
323
+ }
324
+ break;
325
+ }
326
+ return false;
327
+ }
328
+
329
+ private function delete_akismet_metadata( $action ) {
330
+ global $wpdb;
331
+ switch( $action ) {
332
+ case 'is_available' :
333
+ $applies = $wpdb->get_var( "SELECT COUNT(comment_ID) FROM $wpdb->commentmeta WHERE meta_key LIKE 'akismet%';" );
334
+ return array(
335
+ 'title' => 'Delete Akismet Metadata',
336
+ 'action' => 'delete-akismet-metadata',
337
+ 'description' => 'Deletes all metadata attached to comments by the Akismet plugin.',
338
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
339
+ 'has_items' => ! empty( $applies ),
340
+ 'last_run' => $this->get_last_run( 'delete-akismet-metadata' ),
341
+ );
342
+ case 'run' :
343
+ $this->update_last_run_var( 'delete-akismet-metadata' );
344
+ if ( $result = $wpdb->query( "DELETE FROM $wpdb->commentmeta WHERE meta_key LIKE 'akismet%'" ) ) {
345
+ return true;
346
+ }
347
+ break;
348
+ }
349
+ return false;
350
+ }
351
+
352
+ private function delete_expired_transients( $action ) {
353
+ global $wpdb;
354
+ switch( $action ) {
355
+ case 'is_available' :
356
+ $applies = $wpdb->get_var( "SELECT COUNT(option_id) FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_%' AND option_value < " . ( current_time( 'timestamp' ) - WEEK_IN_SECONDS ). ";" );
357
+ return array(
358
+ 'title' => 'Delete Stale Transient Options',
359
+ 'action' => 'delete-expired-transients',
360
+ 'description' => 'Deletes all transient options that expired more than a week ago.',
361
+ 'applies_to' => empty( $applies ) ? '0 items' : $applies . ' items',
362
+ 'has_items' => ! empty( $applies ),
363
+ 'last_run' => $this->get_last_run( 'delete-expired-transients' ),
364
+ );
365
+ case 'run' :
366
+ $this->update_last_run_var( 'delete-expired-transients' );
367
+ if ( $result = $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_%' AND option_value < " . ( current_time( 'timestamp' ) - WEEK_IN_SECONDS ) . ";" ) ) {
368
+ foreach ( (array) $result as $name ) {
369
+ delete_transient( substr( $name, 19 ) );
370
+ }
371
+ if ( 0 === $wpdb->get_var( "SELECT COUNT(option_id) FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_%' AND option_value < " . current_time( 'timestamp' ) . ";" ) ) {
372
+ return true;
373
+ }
374
+ }
375
+ break;
376
+ }
377
+ return false;
378
+ }
379
+
380
+
381
+ private function get_last_run( $optimization_slug ) {
382
+ return empty( $this->optimize_options[$optimization_slug]['last_run'] ) ? __( 'Never', 'it-l10n-ithemes-sync' ) : $this->optimize_options[$optimization_slug]['last_run'];
383
+ }
384
+
385
+ private function update_last_run_var( $optimization_slug ) {
386
+ $this->optimize_options[$optimization_slug]['last_run'] = date( $this->date_format );
387
+ }
388
+ }
verbs/deauthenticate-user.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the deauthenticate-user verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-19 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Deauthenticate_User extends Ithemes_Sync_Verb {
15
+ public static $name = 'deauthenticate-user';
16
+ public static $description = 'Desync a user.';
17
+
18
+ private $default_arguments = array();
19
+
20
+
21
+ public function run( $arguments ) {
22
+ if ( empty( $arguments['user_id'] ) ) {
23
+ $response = new WP_Error( 'missing-user_id', 'The user_id argument is missing. The user could not be desynced.' );
24
+ } else if ( empty( $arguments['username'] ) ) {
25
+ $response = new WP_Error( 'missing-username', 'The username argument is missing. The user could not be desynced.' );
26
+ } else {
27
+ $response = $GLOBALS['ithemes-sync-settings']->remove_authentication( $arguments['user_id'], $arguments['username'] );
28
+
29
+ if ( true === $response ) {
30
+ $response = array( 'success' => 1 );
31
+ }
32
+ }
33
+
34
+ return $response;
35
+ }
36
+ }
verbs/do-update.php ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the do-update verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.2
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-02-18 - Chris Jean
12
+ Updated the responses to better indicate whether updates applied successfully or not.
13
+ 1.2.0 - 2014-03-28 - Chris Jean
14
+ Core updates now provide more information back to the server.
15
+ Multisite updates now properly apply the network upgrade.
16
+ 1.2.1 - 2014-08-22 - Chris Jean
17
+ In order to avoid issues with stale data, update details are now forcibly refreshed before attempting to run updates.
18
+ 1.2.2 - 2015-07-17 - Chris Jean
19
+ An error is no longer sent when a plugin or theme updates to a newer version than the one reported.
20
+ */
21
+
22
+
23
+ class Ithemes_Sync_Verb_Do_Update extends Ithemes_Sync_Verb {
24
+ public static $name = 'do-update';
25
+ public static $description = 'Update WordPress, plugins, and themes.';
26
+
27
+ private $default_arguments = array();
28
+ private $original_update_details;
29
+
30
+
31
+ public function run( $arguments ) {
32
+ require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
33
+ require_once( $GLOBALS['ithemes_sync_path'] . '/upgrader-skin.php' );
34
+
35
+ $this->skin = new Ithemes_Sync_Upgrader_Skin();
36
+
37
+
38
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
39
+
40
+
41
+ $this->original_update_details = Ithemes_Sync_Functions::get_update_details( array( 'verbose' => true, 'force_refresh' => true ) );
42
+
43
+
44
+ $response = array();
45
+
46
+ if ( ! empty( $arguments['plugin'] ) ) {
47
+ $response['plugin'] = $this->do_plugin_upgrade( $arguments['plugin'] );
48
+ }
49
+ if ( ! empty( $arguments['theme'] ) ) {
50
+ $response['theme'] = $this->do_theme_upgrade( $arguments['theme'] );
51
+ }
52
+ if ( ! empty( $arguments['core'] ) ) {
53
+ $response['core'] = $this->do_core_upgrade( $arguments['core'] );
54
+ }
55
+
56
+
57
+ return $response;
58
+ }
59
+
60
+ public function do_core_upgrade( $params ) {
61
+ $required_fields = array(
62
+ 'upgrade_id',
63
+ 'locale',
64
+ 'version',
65
+ );
66
+
67
+ $errors = array();
68
+
69
+ foreach ( $required_fields as $field ) {
70
+ if ( ! isset( $params[$field] ) ) {
71
+ $errors[] = "The '$field' field is missing.";
72
+ }
73
+ }
74
+
75
+
76
+ if ( empty( $errors ) ) {
77
+ require_once( $GLOBALS['ithemes_updater_path'] . '/functions.php' );
78
+
79
+ $updates = Ithemes_Sync_Functions::get_update_details( array( 'verbose' => true, 'force_refresh' => array( 'core' ) ) );
80
+
81
+ if ( empty( $updates['core'] ) ) {
82
+ $errors[] = 'No core updates are currently available.';
83
+ } else if ( empty( $updates['core'][$params['upgrade_id']] ) ) {
84
+ $errors[] = 'Unable to find an availble upgrade matching the requested upgrade_id.';
85
+ } else if ( $params['locale'] != $updates['core'][$params['upgrade_id']]->locale ) {
86
+ $errors[] = 'The requested upgrade does not match the requested locale.';
87
+ } else if ( isset( $updates['core'][$params['upgrade_id']]->version ) && ( $params['version'] != $updates['core'][$params['upgrade_id']]->version ) ) {
88
+ $errors[] = 'The requested upgrade does not match the requested version.';
89
+ } else if ( isset( $updates['core'][$params['upgrade_id']]->current ) && ( $params['version'] != $updates['core'][$params['upgrade_id']]->current ) ) {
90
+ $errors[] = 'The requested upgrade does not match the requested version.';
91
+ }
92
+ }
93
+
94
+ if ( ! empty( $errors ) ) {
95
+ return array( 'errors' => $errors );
96
+ }
97
+
98
+
99
+ Ithemes_Sync_Functions::set_time_limit( 300 );
100
+
101
+ $original_version = Ithemes_Sync_Functions::get_wordpress_version();
102
+
103
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
104
+ require_once( ABSPATH . 'wp-admin/includes/update.php' );
105
+ require_once( ABSPATH . 'wp-admin/includes/misc.php' );
106
+
107
+ $upgrader = new Core_Upgrader( $this->skin );
108
+ $result = $upgrader->upgrade( $updates['core'][$params['upgrade_id']] );
109
+
110
+ Ithemes_Sync_Functions::refresh_core_updates();
111
+
112
+ if ( is_wp_error( $result ) ) {
113
+ return array(
114
+ 'errors' => array(
115
+ $result->get_error_code() => $result->get_error_message(),
116
+ ),
117
+ );
118
+ }
119
+
120
+
121
+ $current_version = Ithemes_Sync_Functions::get_wordpress_version();
122
+ $current_updates = Ithemes_Sync_Functions::get_update_details( array( 'force_refresh' => array( 'core' ) ) );
123
+ $current_update_version = false;
124
+
125
+ foreach ( $current_updates['core'] as $index => $update ) {
126
+ if ( version_compare( $update->version, $current_update_version, '>' ) ) {
127
+ $current_update_version = $update->version;
128
+ }
129
+ }
130
+
131
+
132
+ $response = array(
133
+ 'wordpress_response' => $result,
134
+ 'original_version' => $original_version,
135
+ 'current_version' => $current_version,
136
+ 'current_update_version' => $current_update_version,
137
+ 'original_update_version' => $updates['core'][$params['upgrade_id']]->version,
138
+ );
139
+
140
+
141
+ if ( is_multisite() ) {
142
+ // Based on the upgrade action of wp-admin/network/upgrade.php
143
+
144
+ $wp_db_version = Ithemes_Sync_Functions::get_wordpress_db_version();
145
+ update_site_option( 'wpmu_upgrade_site', $wp_db_version );
146
+
147
+ global $wpdb;
148
+ $blogs = $wpdb->get_results( "SELECT * FROM {$wpdb->blogs} WHERE site_id = '{$wpdb->siteid}' AND spam = '0' AND deleted = '0' AND archived = '0'", ARRAY_A );
149
+
150
+ $response['network_upgrade'] = true;
151
+
152
+
153
+ foreach ( (array) $blogs as $details ) {
154
+ switch_to_blog( $details['blog_id'] );
155
+ $siteurl = site_url();
156
+ $upgrade_url = admin_url( 'upgrade.php?step=upgrade_db' );
157
+ restore_current_blog();
158
+
159
+ $result = wp_remote_get( $upgrade_url, array( 'timeout' => 120, 'httpversion' => '1.1' ) );
160
+
161
+ if ( is_wp_error( $result ) ) {
162
+ $response['network_upgrade'] = false;
163
+ $response['errors'][] = 'Unable to successfully upgrade the network. You may need to visit the network admin dashboard to manually upgrade the network.';
164
+ break;
165
+ }
166
+
167
+ do_action( 'after_mu_upgrade', $result );
168
+ do_action( 'wpmu_upgrade_site', $details['blog_id'] );
169
+ }
170
+ }
171
+
172
+
173
+ if ( $current_version == $params['version'] ) {
174
+ $response['success'] = true;
175
+ }
176
+
177
+
178
+ return $response;
179
+ }
180
+
181
+ public function do_plugin_upgrade( $plugins ) {
182
+ return $this->do_bulk_upgrade( $plugins, 'plugin' );
183
+ }
184
+
185
+ public function do_theme_upgrade( $themes ) {
186
+ return $this->do_bulk_upgrade( $themes, 'theme' );
187
+ }
188
+
189
+ private function do_bulk_upgrade( $packages, $type ) {
190
+ if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
191
+ return new WP_Error( 'unrecognized-bulk-upgrade-type', "An unrecognized type ($type) was passed to do_bulk_upgrade()." );
192
+ }
193
+
194
+
195
+ Ithemes_Sync_Functions::set_time_limit( 300 );
196
+
197
+
198
+ $original_versions = array();
199
+
200
+ foreach ( $packages as $package ) {
201
+ if ( 'plugin' === $type ) {
202
+ $file_data = Ithemes_Sync_Functions::get_file_data( WP_PLUGIN_DIR . "/$package" );
203
+ } else {
204
+ $file_data = Ithemes_Sync_Functions::get_file_data( WP_CONTENT_DIR . "/themes/$package/style.css" );
205
+ }
206
+
207
+ $original_versions[$package] = $file_data['version'];
208
+ }
209
+
210
+
211
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
212
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
213
+ require_once( ABSPATH . 'wp-admin/includes/misc.php' );
214
+
215
+
216
+ if ( 'plugin' === $type ) {
217
+ $upgrader = new Plugin_Upgrader( $this->skin );
218
+ $result = $upgrader->bulk_upgrade( $packages );
219
+ Ithemes_Sync_Functions::refresh_plugin_updates();
220
+ } else {
221
+ $upgrader = new Theme_Upgrader( $this->skin );
222
+ $result = $upgrader->bulk_upgrade( $packages );
223
+ Ithemes_Sync_Functions::refresh_theme_updates();
224
+ }
225
+
226
+
227
+ if ( is_wp_error( $result ) ) {
228
+ return array(
229
+ 'errors' => array(
230
+ $result->get_error_code() => $result->get_error_message(),
231
+ ),
232
+ );
233
+ } else if ( false === $result ) {
234
+ if ( 'plugin' === $type ) {
235
+ $result = $upgrader->fs_connect( array( WP_CONTENT_DIR, WP_PLUGIN_DIR ) );
236
+ } else {
237
+ $result = $upgrader->fs_connect( array( WP_CONTENT_DIR ) );
238
+ }
239
+
240
+
241
+ if ( is_wp_error( $result ) ) {
242
+ return array(
243
+ 'errors' => array(
244
+ $result->get_error_code() => $result->get_error_message(),
245
+ ),
246
+ );
247
+ } else {
248
+ return array(
249
+ 'errors' => array(
250
+ 'non-connected-filesystem' => 'Unable to update due to a non-connected filesystem.',
251
+ ),
252
+ );
253
+ }
254
+ }
255
+
256
+
257
+ $update_details = Ithemes_Sync_Functions::get_update_details( array( 'verbose' => true ) );
258
+ $response = array();
259
+
260
+ $update_index = "{$type}s";
261
+
262
+ foreach ( $result as $package => $info ) {
263
+ if ( false === $info ) {
264
+ $response[$package]['errors']['non-connected-filesystem'] = 'Unable to update due to a non-connected filesystem.';
265
+ } else if ( is_wp_error( $info ) ) {
266
+ $response[$package]['errors'][$info->get_error_code()] = $info->get_error_message();
267
+ } else {
268
+ $response[$package]['wordpress_response'] = $info;
269
+
270
+ if ( 'plugin' === $type ) {
271
+ $file_data = Ithemes_Sync_Functions::get_file_data( WP_PLUGIN_DIR . "/$package" );
272
+ } else {
273
+ $file_data = Ithemes_Sync_Functions::get_file_data( WP_CONTENT_DIR . "/themes/$package/style.css" );
274
+ }
275
+
276
+ $response[$package]['current_version'] = $file_data['version'];
277
+
278
+ if ( isset( $original_versions[$package] ) ) {
279
+ $response[$package]['original_version'] = $original_versions[$package];
280
+ }
281
+ if ( isset( $update_details[$update_index][$package] ) ) {
282
+ if ( ( 'plugin' === $type ) && isset( $update_details[$update_index][$package]->new_version ) ) {
283
+ $response[$package]['current_update_version'] = $update_details[$update_index][$package]->new_version;
284
+ } else if ( ( 'theme' === $type ) && isset( $update_details[$update_index][$package]['new_version'] ) ) {
285
+ $response[$package]['current_update_version'] = $update_details[$update_index][$package]['new_version'];
286
+ }
287
+ }
288
+ if ( isset( $this->original_update_details[$update_index][$package] ) ) {
289
+ if ( ( 'plugin' === $type ) && isset( $this->original_update_details[$update_index][$package]->new_version ) ) {
290
+ $response[$package]['original_update_version'] = $this->original_update_details[$update_index][$package]->new_version;
291
+ } else if ( ( 'theme' === $type ) && isset( $this->original_update_details[$update_index][$package]['new_version'] ) ) {
292
+ $response[$package]['original_update_version'] = $this->original_update_details[$update_index][$package]['new_version'];
293
+ }
294
+ }
295
+
296
+
297
+ if ( 'plugin' === $type ) {
298
+ $removed_old_update_data = $GLOBALS['ithemes_sync_request_handler']->remove_old_update_plugins_data( $package );
299
+
300
+ if ( ! is_null( $removed_old_update_data ) ) {
301
+ $response[$package]['removed_old_update_data'] = $removed_old_update_data;
302
+ }
303
+ }
304
+
305
+
306
+ if ( ! isset( $response[$package]['original_update_version'] ) ) {
307
+ $response[$package]['errors']['no-update'] = 'No update was available.';
308
+ } else if ( version_compare( $response[$package]['current_version'], $response[$package]['original_update_version'], '>=' ) ) {
309
+ $response[$package]['success'] = 1;
310
+
311
+ if ( isset( $response[$package]['current_update_version'] ) ) {
312
+ if ( version_compare( $response[$package]['current_version'], $response[$package]['current_update_version'], '>=' ) ) {
313
+ $response[$package]['errors']['old-update-remains-available'] = 'The original update is still listed despite the update working properly.';
314
+ } else {
315
+ $response[$package]['errors']['new-update-available'] = 'An update is available.';
316
+ }
317
+ }
318
+ } else {
319
+ $response[$package]['errors']['unknown-error'] = 'An unknown error prevented the update from completing successfully.';
320
+ }
321
+ }
322
+ }
323
+
324
+ return $response;
325
+ }
326
+ }
verbs/get-admin-bar-item-whitelist.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Implementation of the get-admin-bar-item-whitelist verb.
5
+ *
6
+ * @author Aaron D. Campbell <aaron@ithemes.com>
7
+ * @version 1.0.0
8
+ *
9
+ * Version History
10
+ * 1.0.0 - 2014-10-01 - Aaron D. Campbell
11
+ * Initial version
12
+ */
13
+
14
+
15
+ class Ithemes_Sync_Verb_Get_Admin_Bar_Item_Whitelist extends Ithemes_Sync_Verb {
16
+ public static $name = 'get-admin-bar-item-whitelist';
17
+ public static $description = 'Get the admin bar items a user can see.';
18
+
19
+ public function run( $arguments ) {
20
+ if ( empty( $arguments['id'] ) ) {
21
+ return WP_Error( 'user-id-required', 'User ID Required' );
22
+ }
23
+
24
+ $user = get_user_by( 'id', absint( $arguments['id'] ) );
25
+ if ( ! is_object( $user ) || ! is_a( $user, 'WP_User' ) ) {
26
+ return WP_Error( 'invalid-user-id', "Unable to find the requested user with an ID of {$arguments['id']}." );
27
+ }
28
+
29
+ $meta_key = 'ithemes-sync-admin-bar-item-whitelist-' . get_current_blog_id();
30
+ return get_user_meta( $user->ID, $meta_key, true );
31
+ }
32
+ }
verbs/get-admin-bar-items.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Implementation of the get-admin-bar-items verb.
5
+ *
6
+ * @author Aaron D. Campbell <aaron@ithemes.com>
7
+ * @version 1.0.0
8
+ *
9
+ * Version History
10
+ * 1.0.0 - 2014-10-01 - Aaron D. Campbell
11
+ * Initial version
12
+ */
13
+
14
+
15
+ class Ithemes_Sync_Verb_Get_Admin_Bar_Items extends Ithemes_Sync_Verb {
16
+ public static $name = 'get-admin-bar-items';
17
+ public static $description = 'Retrieve list of admin bar items for a user.';
18
+
19
+ public function run( $arguments ) {
20
+ if ( empty( $arguments['id'] ) ) {
21
+ return array( 'error' => 'User ID Required' );
22
+ }
23
+
24
+ $user = get_user_by( 'id', absint( $arguments['id'] ) );
25
+ if ( ! is_object( $user ) || ! is_a( $user, 'WP_User' ) ) {
26
+ return WP_Error( 'invalid-user-id', "Unable to find the requested user with an ID of {$arguments['id']}." );
27
+ }
28
+
29
+ $meta_key = 'ithemes-sync-admin-bar-items-' . get_current_blog_id();
30
+ return get_user_meta( $user->ID, $meta_key, true );
31
+ }
32
+ }
verbs/get-admin-menu.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Implementation of the get-admin-menu verb.
5
+ *
6
+ * @author Aaron D. Campbell <aaron@ithemes.com>
7
+ * @version 1.0.0
8
+ *
9
+ * Version History
10
+ * 1.0.0 - 2014-09-02 - Aaron D. Campbell
11
+ * Initial version
12
+ */
13
+
14
+
15
+ class Ithemes_Sync_Verb_Get_Admin_Menu extends Ithemes_Sync_Verb {
16
+ public static $name = 'get-admin-menu';
17
+ public static $description = 'Retrieve admin menu items.';
18
+
19
+ public function run( $arguments ) {
20
+ return get_option( 'ithemes-sync-admin_menu' );
21
+ }
22
+ }
verbs/get-authentication-token.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-authentication-token verb.
5
+ Written by Lew Ayotte for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.1 - 2016-08-36 - Lew Ayotte
10
+ Added `login as user` support
11
+ 1.0.0 - 2016-08-22 - Lew Ayotte
12
+ Initial version
13
+ */
14
+
15
+ class Ithemes_Sync_Verb_Get_Authentication_Token extends Ithemes_Sync_Verb {
16
+ public static $name = 'get-authentication-token';
17
+ public static $description = 'Get authentication token from the WordPress authentication system.';
18
+
19
+ private $default_arguments = array();
20
+ private $response = array();
21
+
22
+ private $auth_cookie;
23
+ private $logged_in_cookie;
24
+
25
+
26
+ public function run( $arguments ) {
27
+ $response = array(
28
+ 'site_url' => admin_url(), //Default to the wp-admin URL if something goes wrong.
29
+ );
30
+ $path = !empty( $arguments['path'] ) ? $arguments['path'] : '';
31
+ $path_data = !empty( $arguments['path_data'] ) ? $arguments['path_data'] : '';
32
+
33
+ $auth_details = $GLOBALS['ithemes-sync-settings']->get_authentication_details( $arguments['user_id'] );
34
+ if ( !empty( $auth_details ) ) { //Make sure the synced user is still authenticated to this site
35
+ if ( !empty( $arguments['wp_user_id'] ) ) { //If wp_user_id is passed, log in as that user.
36
+ $user = get_user_by( 'id', $arguments['wp_user_id'] );
37
+ } else { //Otherwise log in as the sync'ed user
38
+ $user = get_user_by( 'login', $auth_details['local_user'] );
39
+ }
40
+
41
+ if ( !empty( $user ) ) {
42
+ $key = bin2hex( random_bytes( 16 ) );
43
+ $response = array(
44
+ 'site_url' => add_query_arg( 'sync-login', $key, site_url() ),
45
+ );
46
+ update_option( 'sync_login_' . $key, array( 'user_id' => $user->ID, 'path' => $path, 'path_data' => $path_data, 'expires' => current_time( 'timestamp', true ) + 90 ) );
47
+ }
48
+ }
49
+
50
+ return $response;
51
+ }
52
+ }
verbs/get-comment-details.php ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-comment-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-06-02 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Comment_Details extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-comment-details';
16
+ public static $description = 'Retrieve details about the site\'s comments.';
17
+ public static $status_element_name = 'comments';
18
+ public static $show_in_status_by_default = false;
19
+
20
+ private $response = array();
21
+
22
+ private $default_arguments = array(
23
+ 'args' => array(
24
+ 'status' => 'hold',
25
+ ),
26
+ 'include-parent-details' => true,
27
+ 'include-post-details' => true,
28
+ 'include-user-details' => true,
29
+ 'include-comment-counts' => true,
30
+ );
31
+
32
+ public function run( $arguments ) {
33
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
34
+
35
+ if ( ! is_callable( 'get_comments' ) ) {
36
+ include_once( ABSPATH . WPINC . '/wp-includes/comment.php' );
37
+ }
38
+ if ( ! is_callable( 'get_comments' ) ) {
39
+ return new WP_Error( 'missing-function-get_comments', 'Due to an unknown issue, the wp_comments function is not available.' );
40
+ }
41
+
42
+ $comments = get_comments( $arguments['args'] );
43
+ $comments_count = wp_count_comments();
44
+
45
+ if ( is_array( $comments ) ) {
46
+ $this->response['comments'] = array();
47
+
48
+ foreach ( $comments as $index => $comment ) {
49
+ $this->response['comments'][$comment->comment_ID] = (array) $comment;
50
+ }
51
+
52
+ if ( !empty( $arguments['include-parent-details'] ) ) {
53
+ $this->add_parent_details();
54
+ }
55
+
56
+ if ( !empty( $arguments['include-post-details'] ) ) {
57
+ $this->add_post_details();
58
+ }
59
+
60
+ if ( !empty( $arguments['include-user-details'] ) ) {
61
+ $this->add_user_details();
62
+ }
63
+
64
+ if ( !empty( $arguments['include-comment-counts'] ) ) {
65
+ $this->response['comment_counts'] = $comments_count;
66
+ }
67
+ } else {
68
+ $this->response = $comments;
69
+ }
70
+
71
+
72
+ return $this->response;
73
+ }
74
+
75
+ private function add_parent_details() {
76
+ $parent_ids = array();
77
+ $parents = array();
78
+
79
+ foreach ( $this->response['comments'] as $comment ) {
80
+ if ( ! empty( $comment['comment_parent'] ) ) {
81
+ $parent_ids[$comment['comment_parent']] = true;
82
+ }
83
+ }
84
+ $parent_ids = array_keys( $parent_ids );
85
+
86
+ foreach ( $parent_ids as $parent_id ) {
87
+ $parents[$parent_id] = get_comment( $parent_id, ARRAY_A );
88
+ }
89
+
90
+ $this->response['parents'] = $parents;
91
+ }
92
+
93
+ private function add_post_details() {
94
+ $post_ids = array();
95
+ $posts = array();
96
+
97
+ foreach ( $this->response['comments'] as $id => $comment ) {
98
+ $post_ids[$comment['comment_post_ID']] = true;
99
+ }
100
+ $post_ids = array_keys( $post_ids );
101
+
102
+ if ( ! empty( $post_ids ) ) {
103
+ $posts = get_posts( array( 'post__in' => $post_ids ) );
104
+ }
105
+
106
+ foreach ( $posts as $post ) {
107
+ $id = $post->ID;
108
+
109
+ $post = (array) $post;
110
+ unset( $post['ID'] );
111
+ unset( $post['post_content'] );
112
+ unset( $post['post_excerpt'] );
113
+ unset( $post['post_password'] );
114
+ unset( $post['post_modified'] );
115
+ unset( $post['post_modified_gmt'] );
116
+ unset( $post['to_ping'] );
117
+ unset( $post['pinged'] );
118
+ unset( $post['post_content_filtered'] );
119
+ unset( $post['menu_order'] );
120
+ unset( $post['filter'] );
121
+
122
+ $this->response['posts'][$id] = $post;
123
+ }
124
+ }
125
+
126
+ private function add_user_details() {
127
+ $user_ids = array();
128
+
129
+ $comments = $this->response['comments'];
130
+
131
+ if ( isset( $this->response['parents'] ) ) {
132
+ $comments = array_merge( $comments, $this->response['parents'] );
133
+ }
134
+
135
+ foreach ( $comments as $comment ) {
136
+ if ( empty( $comment['user_id'] ) ) {
137
+ continue;
138
+ }
139
+
140
+ $user_ids[$comment['user_id']] = true;
141
+ }
142
+
143
+ if ( isset( $this->response['posts'] ) ) {
144
+ foreach ( $this->response['posts'] as $post ) {
145
+ $user_ids[$post['post_author']] = true;
146
+ }
147
+ }
148
+
149
+ $user_ids = array_keys( $user_ids );
150
+
151
+ if ( empty( $user_ids ) ) {
152
+ $this->response['users'] = array();
153
+ return;
154
+ }
155
+
156
+
157
+ $users = get_users( array( 'include' => $user_ids ) );
158
+
159
+ foreach ( $users as $user ) {
160
+ $id = $user->ID;
161
+
162
+ $user = (array) $user->data;
163
+ unset( $user['ID'] );
164
+ unset( $user['user_pass'] );
165
+ unset( $user['user_login'] );
166
+ unset( $user['user_nicename'] );
167
+ unset( $user['user_activation_key'] );
168
+ unset( $user['user_status'] );
169
+
170
+ $this->response['users'][$id] = $user;
171
+ }
172
+ }
173
+ }
verbs/get-dashboard-widgets.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Implementation of the get-dashboard-widgets verb.
5
+ *
6
+ * @author Aaron D. Campbell <aaron@ithemes.com>
7
+ * @version 1.0.0
8
+ *
9
+ * Version History
10
+ * 1.0.0 - 2014-09-12 - Aaron D. Campbell
11
+ * Initial version
12
+ */
13
+
14
+
15
+ class Ithemes_Sync_Verb_Get_Dashboard_Widgets extends Ithemes_Sync_Verb {
16
+ public static $name = 'get-dashboard-widgets';
17
+ public static $description = 'Retrieve list of dashboard widgets.';
18
+
19
+ public function run( $arguments ) {
20
+ return get_option( 'ithemes-sync-dashboard-metaboxes' );
21
+ }
22
+ }
verbs/get-gf-form-entries.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Implementation of the get-gf-form-entries verb.
5
+ *
6
+ * @author Aaron D. Campbell <aaron@ithemes.com>
7
+ * @version 1.0.0
8
+ *
9
+ * Version History
10
+ * 1.0.0 - 2014-07-07 - Aaron D. Campbell
11
+ * Initial version
12
+ */
13
+
14
+
15
+ class Ithemes_Sync_Verb_Get_GF_Form_Entries extends Ithemes_Sync_Verb {
16
+ public static $name = 'get-gf-form-entries';
17
+ public static $description = 'Retrieve Gravity Forms form entries.';
18
+
19
+ public function run( $arguments ) {
20
+ if ( class_exists( 'RGFormsModel' ) && is_callable( array( 'RGFormsModel', 'get_form_meta' ) )
21
+ && class_exists( 'GFAPI' ) && is_callable( array( 'GFAPI', 'get_entries' ) ) ) {
22
+
23
+ if ( empty( $arguments['id'] ) ) {
24
+ return array( 'error' => 'Form ID Required' );
25
+ }
26
+ $return = array();
27
+ $form_meta = GFFormsModel::get_form_meta( absint( $arguments['id'] ) );
28
+ $return['field_labels'] = array();
29
+ foreach ( $form_meta['fields'] as $field ) {
30
+ $return['field_labels'][ "{$field['id']}" ] = $this->_process_label( $field );
31
+ }
32
+
33
+ $return['current_page'] = empty( $arguments['page'] ) ? 1 : absint( $arguments['page'] );
34
+ if ( $return['current_page'] < 1 ) {
35
+ $return['current_page'] = 1;
36
+ }
37
+
38
+ $paging = array(
39
+ 'page_size' => 20,
40
+ );
41
+ $paging['offset'] = ( $return['current_page'] - 1 ) * $paging['page_size'];
42
+
43
+ $return['total_count'] = 0;
44
+ $return['entries'] = GFAPI::get_entries( absint( $arguments['id'] ), null, null, $paging, $return['total_count'] );
45
+ $return['page_size'] = $paging['page_size'];
46
+ $return['total_pages'] = ceil( $return['total_count'] / $paging['page_size'] );
47
+ return $return;
48
+ }
49
+ return false;
50
+ }
51
+
52
+ private function _process_label( $field ) {
53
+ $return = array( 'label' => $field['label'] );
54
+
55
+ if ( ! empty( $field['inputs'] ) ) {
56
+ $return['children'] = array();
57
+ foreach ( $field['inputs'] as $input ) {
58
+ $return['children'][ "{$input['id']}" ] = $this->_process_label( $input );
59
+ }
60
+ }
61
+
62
+ return $return;
63
+ }
64
+ }
verbs/get-gf-forms.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Implementation of the get-gf-forms verb.
5
+ *
6
+ * @author Aaron D. Campbell <aaron@ithemes.com>
7
+ * @version 1.0.0
8
+ *
9
+ * Version History
10
+ * 1.0.0 - 2014-07-07 - Aaron D. Campbell
11
+ * Initial version
12
+ */
13
+
14
+
15
+ class Ithemes_Sync_Verb_Get_GF_Forms extends Ithemes_Sync_Verb {
16
+ public static $name = 'get-gf-forms';
17
+ public static $description = 'Retrieve Gravity Forms forms.';
18
+
19
+ public function run( $arguments ) {
20
+ if ( class_exists( 'RGFormsModel' ) && is_callable( array( 'RGFormsModel', 'get_forms' ) ) ) {
21
+ return RGFormsModel::get_forms();
22
+ }
23
+ return false;
24
+ }
25
+ }
verbs/get-notices.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-updates verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-03-28 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Notices extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-notices';
16
+ public static $description = 'Receive notices generated by other code running on the site.';
17
+ public static $status_element_name = 'notices';
18
+ public static $show_in_status_by_default = true;
19
+
20
+ private $default_arguments = array(
21
+ 'clear-urgent-notices' => true,
22
+ );
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ require_once( $GLOBALS['ithemes_sync_path'] . '/notice-handler.php' );
29
+
30
+ $normal = $GLOBALS['ithemes_sync_notice_handler']->get_notices( $arguments );
31
+
32
+ $urgent = $GLOBALS['ithemes_sync_notice_handler']->get_urgent_notices();
33
+
34
+ if ( $arguments['clear-urgent-notices'] ) {
35
+ $GLOBALS['ithemes_sync_notice_handler']->clear_urgent_notices();
36
+ }
37
+
38
+ return compact( 'normal', 'urgent' );
39
+ }
40
+ }
verbs/get-options.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-options verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-07-01 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Options extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-options';
16
+ public static $description = 'Retrieve values from the WordPress options system.';
17
+ public static $status_element_name = 'options';
18
+ public static $show_in_status_by_default = false;
19
+
20
+ private $default_arguments = array();
21
+
22
+ private $response = array();
23
+
24
+ public function run( $arguments ) {
25
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
26
+
27
+ foreach ( $arguments as $var => $val ) {
28
+ if ( 'options' == $var ) {
29
+ $this->response[$var] = $this->get_values( $var, $val, 'get_option', 'wp_load_alloptions' );
30
+ } else if ( 'site-options' == $var ) {
31
+ $this->response[$var] = $this->get_values( $var, $val, 'get_site_option' );
32
+ } else if ( 'transients' == $var ) {
33
+ $this->response[$var] = $this->get_values( $var, $val, 'get_transient' );
34
+ } else if ( 'site-transients' == $var ) {
35
+ $this->response[$var] = $this->get_values( $var, $val, 'get_site_transient' );
36
+ } else {
37
+ $this->response[$var] = new WP_Error( 'invalid-argument', "The $var argument is not recognized by the get-options verb." );
38
+ }
39
+ }
40
+
41
+ return $this->response;
42
+ }
43
+
44
+ private function get_values( $argument, $names, $function, $all_function = false ) {
45
+ if ( true === $names ) {
46
+ if ( false === $all_function ) {
47
+ return new WP_Error( 'invalid-argument-value', "The $argument argument does not support listing all availble options. Please supply an array of strings." );
48
+ } else if ( is_callable( $all_function ) ) {
49
+ $options = call_user_func( $all_function );
50
+ unset( $options['ithemes-sync-cache'] );
51
+
52
+ return $options;
53
+ } else {
54
+ return new WP_Error( "missing-function-$all_function", "An unknown error is preventing the function for listing all $argument from being callable." );
55
+ }
56
+ } else if ( is_array( $names ) || is_string( $names ) ) {
57
+ $options = array();
58
+
59
+ foreach ( (array) $names as $name ) {
60
+ $options[$name] = call_user_func( $function, $name );
61
+ }
62
+
63
+ return $options;
64
+ } else {
65
+ if ( false === $all_function ) {
66
+ return new WP_Error( 'invalid-argument-value', "The $argument argument requires an array of strings." );
67
+ } else {
68
+ return new WP_Error( 'invalid-argument-value', "The $argument argument requires an array of strings or a boolean true." );
69
+ }
70
+ }
71
+
72
+ }
73
+ }
verbs/get-php-details.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-php-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-14 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name and $show_in_status_by_default.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_PHP_Details extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-php-details';
18
+ public static $description = 'Retrieve details about the PHP configuration that is handling requests.';
19
+ public static $status_element_name = 'php';
20
+ public static $show_in_status_by_default = true;
21
+
22
+ private $default_arguments = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ return Ithemes_Sync_Functions::get_php_details( $arguments );
29
+ }
30
+ }
verbs/get-plugin-details.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-plugin-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-14 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name and $show_in_status_by_default.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_Plugin_Details extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-plugin-details';
18
+ public static $description = 'Retrieve details about the installed plugins.';
19
+ public static $status_element_name = 'plugins';
20
+ public static $show_in_status_by_default = true;
21
+
22
+ private $default_arguments = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ return Ithemes_Sync_Functions::get_plugin_details( $arguments );
29
+ }
30
+ }
verbs/get-post-types.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-post-types verb.
5
+ Written by Lew Ayotte for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2016-10-17 - Lew Ayotte
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Post_Types extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-post-types';
16
+ public static $description = 'Retrieve post types from WordPress.';
17
+ public static $status_element_name = 'post-types';
18
+
19
+ private $default_arguments = array(
20
+ 'args' => array(),
21
+ 'output' => 'names',
22
+ 'operator' => 'and',
23
+ );
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+ return get_post_types( $arguments['args'], $arguments['output'], $arguments['operator'] );
28
+ }
29
+ }
verbs/get-posts.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-posts verb.
5
+ Written by Aaron D. Campbell for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2015-02-17 - Aaron D. Campbell
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Posts extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-posts';
16
+ public static $description = 'Retrieve posts from WordPress.';
17
+ public static $status_element_name = 'posts';
18
+ public static $show_in_status_by_default = false;
19
+
20
+ private $default_arguments = array(
21
+ 'args' => array(
22
+ 'post_type' => 'post',
23
+ 'numberposts' => 10,
24
+ ),
25
+ 'include-comment-details' => true,
26
+ 'include-post-counts' => true,
27
+ );
28
+
29
+ public function run( $arguments ) {
30
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
31
+ $results = array();
32
+ $posts = get_posts( $arguments['args'] );
33
+
34
+ // Add some additional data
35
+ foreach ( $posts as &$post ) {
36
+ $post->permalink = get_permalink( $post->ID );
37
+ $post->author_display_name = get_the_author_meta( 'display_name', $post->post_author );
38
+
39
+ if ( !empty( $arguments['include-comment-details'] ) ) {
40
+ $post->comment_counts = $this->add_comment_details( $post->ID );
41
+ }
42
+
43
+ // Yoast SEO Verbs
44
+ if ( is_plugin_active( 'wordpress-seo/wp-seo.php' ) ) {
45
+ $post->yoast_seo = $this->add_yoast_seo_details( $post->ID );
46
+ }
47
+
48
+ }
49
+
50
+ if ( !empty( $arguments['include-post-counts'] ) ) {
51
+ $results['posts'] = $posts;
52
+ $results['post_counts'] = wp_count_posts( $arguments['args']['post_type'] );
53
+ } else {
54
+ $results = $posts;
55
+ }
56
+
57
+ return $results;
58
+ }
59
+
60
+ private function add_comment_details( $post_id ) {
61
+ return wp_count_comments( $post_id );
62
+ }
63
+
64
+ private function add_yoast_seo_details( $post_id ) {
65
+ if ( '1' === WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) ) {
66
+ $score = new WPSEO_Rank( WPSEO_Rank::NO_INDEX );
67
+ } elseif ( '' === WPSEO_Meta::get_value( 'focuskw', $post_id ) ) {
68
+ $score = new WPSEO_Rank( WPSEO_Rank::NO_FOCUS );
69
+ } else {
70
+ $score = (int) WPSEO_Meta::get_value( 'linkdex', $post_id );
71
+ $score = WPSEO_Rank::from_numeric_score( $score );
72
+ }
73
+
74
+ $score = $score->get_label();
75
+ $content_score = (int) WPSEO_Meta::get_value( 'content_score', $post_id );
76
+ $readability = WPSEO_Rank::from_numeric_score( $content_score );
77
+ $readability = $readability->get_label();
78
+
79
+ $keyword = WPSEO_Meta::get_value( 'focuskw', $post_id );
80
+
81
+ return array( 'score' => $score, 'readability' => $readability, 'keyword' => $keyword );
82
+
83
+ }
84
+ }
verbs/get-role-details.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-role-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-06-02 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Role_Details extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-role-details';
16
+ public static $description = 'Retrieve details about the site\'s user roles.';
17
+ public static $status_element_name = 'roles';
18
+ public static $show_in_status_by_default = false;
19
+
20
+ private $default_arguments = array(
21
+ 'just-names' => false,
22
+ );
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ require_once( ABSPATH . 'wp-admin/includes/user.php' );
29
+
30
+ $roles = get_editable_roles();
31
+
32
+ if ( $arguments['just-names'] ) {
33
+ foreach ( $roles as $index => $data ) {
34
+ $roles[$index] = $data['name'];
35
+ }
36
+ }
37
+
38
+ return $roles;
39
+ }
40
+ }
verbs/get-server-details.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-server-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-14 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name and $show_in_status_by_default.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_Server_Details extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-server-details';
18
+ public static $description = 'Retrieve details about the server.';
19
+ public static $status_element_name = 'server';
20
+ public static $show_in_status_by_default = true;
21
+
22
+ private $default_arguments = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ return Ithemes_Sync_Functions::get_server_details( $arguments );
29
+ }
30
+ }
verbs/get-status-elements.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-status-elements verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-01-09 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Status_Elements extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-status-elements';
16
+ public static $description = 'Retrieve a listing of the status elements.';
17
+ public static $status_element_name = 'status-elements';
18
+ public static $show_in_status_by_default = true;
19
+
20
+ private $default_arguments = array();
21
+
22
+
23
+ public function run( $arguments ) {
24
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
25
+
26
+ return array_keys( Ithemes_Sync_Functions::get_status_elements( $arguments ) );
27
+ }
28
+ }
verbs/get-status.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-status verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.4.1
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2013-11-18 - Chris Jean
12
+ Updated code to use external functions to pull the data from.
13
+ Added "php", "server", and "settings" sections.
14
+ 1.2.0 - 2013-11-22 - Chris Jean
15
+ Added "supported-verbs" section.
16
+ 1.3.0 - 2013-12-19 - Chris Jean
17
+ Updated to support variable element output.
18
+ 1.4.0 - 2014-01-20 - Chris Jean
19
+ Enhancement: The status elements are now variable, allowing for requests for specific data.
20
+ 1.4.1 - 2014-06-23 - Chris Jean
21
+ Added the ability to provide arguments specific to individual status elements.
22
+ */
23
+
24
+
25
+ class Ithemes_Sync_Verb_Get_Status extends Ithemes_Sync_Verb {
26
+ public static $name = 'get-status';
27
+ public static $description = 'Retrieve basic details about the site.';
28
+
29
+ private $default_arguments = array(
30
+ 'status_elements' => array(),
31
+ );
32
+
33
+
34
+ public function run( $arguments ) {
35
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
36
+
37
+ if ( ! is_callable( array( $GLOBALS['ithemes-sync-api'], 'run' ) ) ) {
38
+ return new WP_Error( 'missing-method-api-run', 'The Ithemes_Sync_API::run function is not callable. Unable to generate status details.' );
39
+ }
40
+
41
+
42
+ $status_elements = Ithemes_Sync_Functions::get_status_elements( $arguments );
43
+
44
+ if ( ! empty( $arguments['status_elements'] ) ) {
45
+ if ( is_array( $arguments['status_elements'] ) ) {
46
+ $show_status_elements = $arguments['status_elements'];
47
+ } else {
48
+ trigger_error( 'A non-array status_elements argument was supplied. The argument will be ignored.' );
49
+ }
50
+
51
+ unset( $arguments['status_elements'] );
52
+ }
53
+
54
+ if ( ! isset( $show_status_elements ) ) {
55
+ $show_status_elements = Ithemes_Sync_Functions::get_default_status_elements();
56
+ }
57
+
58
+ foreach ( $show_status_elements as $element ) {
59
+ if ( isset( $status_elements[$element] ) ) {
60
+ $var = $status_elements[$element];
61
+
62
+ $element_arguments = $arguments;
63
+
64
+ if ( isset( $arguments[$var] ) ) {
65
+ $element_arguments = $arguments[$var];
66
+ }
67
+
68
+ $data = $GLOBALS['ithemes-sync-api']->run( $status_elements[$element], $element_arguments );
69
+ } else {
70
+ $data = "This element is not recognized";
71
+ }
72
+
73
+ $status[$element] = $data;
74
+ }
75
+
76
+
77
+ return $status;
78
+ }
79
+ }
verbs/get-supported-verbs.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-supported-verbs verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2013-11-22 - Chris Jean
12
+ Simplified the run() function by having it simply call the Ithemes_Sync_Functions::get_supported_verbs() function.
13
+ 1.2.0 - 2014-01-20 - Chris Jean
14
+ Added $status_element_name and $show_in_status_by_default.
15
+ */
16
+
17
+
18
+ class Ithemes_Sync_Verb_Get_Supported_Verbs extends Ithemes_Sync_Verb {
19
+ public static $name = 'get-supported-verbs';
20
+ public static $description = 'Retrieve a listing of the supported verbs.';
21
+ public static $status_element_name = 'supported-verbs';
22
+ public static $show_in_status_by_default = true;
23
+
24
+ private $default_arguments = array();
25
+
26
+
27
+ public function run( $arguments ) {
28
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
29
+
30
+ return Ithemes_Sync_Functions::get_supported_verbs( $arguments );
31
+ }
32
+ }
verbs/get-sync-settings.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-sync-settings verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-20 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name and $show_in_status_by_default.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_Sync_Settings extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-sync-settings';
18
+ public static $description = 'Retrieve the Sync plugin\'s settings.';
19
+ public static $status_element_name = 'settings';
20
+ public static $show_in_status_by_default = true;
21
+
22
+ private $default_arguments = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ return Ithemes_Sync_Functions::get_sync_settings( $arguments );
29
+ }
30
+ }
verbs/get-theme-details.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-theme-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-14 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name and $show_in_status_by_default.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_Theme_Details extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-theme-details';
18
+ public static $description = 'Retrieve details about the installed themes.';
19
+ public static $status_element_name = 'themes';
20
+ public static $show_in_status_by_default = true;
21
+
22
+ private $default_arguments = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ return Ithemes_Sync_Functions::get_theme_details( $arguments );
29
+ }
30
+ }
verbs/get-update-details.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-update-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-14 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name and $show_in_status_by_default.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_Update_Details extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-update-details';
18
+ public static $description = 'Find details about all available updates on the site. This includes details about updates to WordPress, plugins, themes, and translations.';
19
+ public static $status_element_name = 'updates';
20
+ public static $show_in_status_by_default = true;
21
+
22
+ private $default_arguments = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ return Ithemes_Sync_Functions::get_update_details( $arguments );
29
+ }
30
+ }
verbs/get-updates.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-updates verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2013-11-18 - Chris Jean
12
+ Passed $arguments array to get_update_details() function.
13
+ 1.2.0 - 2014-01-20 - Chris Jean
14
+ Added $status_element_name and $show_in_status_by_default.
15
+ */
16
+
17
+
18
+ class Ithemes_Sync_Verb_Get_Updates extends Ithemes_Sync_Verb {
19
+ public static $name = 'get-updates';
20
+ public static $description = 'Find details about all available updates on the site. This includes details about updates to WordPress, plugins, themes, and translations.';
21
+ public static $status_element_name = 'updates';
22
+ public static $show_in_status_by_default = true;
23
+
24
+ private $default_arguments = array();
25
+
26
+
27
+ public function run( $arguments ) {
28
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
29
+
30
+ return Ithemes_Sync_Functions::get_update_details( $arguments );
31
+ }
32
+ }
verbs/get-user-details.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-user-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-06-02 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-07-23 - Chris Jean
12
+ Added first_name, last_name, nickname, description, user_level, primary_blog, and source_domain to the user data.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_User_Details extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-user-details';
18
+ public static $description = 'Retrieve details about the site\'s users.';
19
+ public static $status_element_name = 'users';
20
+ public static $show_in_status_by_default = false;
21
+
22
+ private $default_arguments = array(
23
+ 'args' => array(),
24
+ );
25
+
26
+
27
+ public function run( $arguments ) {
28
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
29
+
30
+ $users = get_users( $arguments['args'] );
31
+
32
+ foreach ( $users as $index => $user ) {
33
+ $new_user = (array) $user;
34
+ $new_user['data'] = (array) $new_user['data'];
35
+
36
+ $new_user['data']['first_name'] = $user->first_name;
37
+ $new_user['data']['last_name'] = $user->last_name;
38
+ $new_user['data']['nickname'] = $user->nickname;
39
+ $new_user['data']['description'] = $user->description;
40
+ $new_user['data']['user_level'] = $user->user_level;
41
+
42
+ if ( is_multisite() ) {
43
+ $new_user['data']['primary_blog'] = $user->primary_blog;
44
+ $new_user['data']['source_domain'] = $user->source_domain;
45
+ }
46
+
47
+ $users[$index] = $new_user;
48
+ }
49
+
50
+ return $users;
51
+ }
52
+ }
verbs/get-wordpress-details.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-wordpress-details verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-14 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name and $show_in_status_by_default.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_Wordpress_Details extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-wordpress-details';
18
+ public static $description = 'Retrieve details about the WordPress installation.';
19
+ public static $status_element_name = 'wordpress';
20
+ public static $show_in_status_by_default = true;
21
+
22
+ private $default_arguments = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ return Ithemes_Sync_Functions::get_wordpress_details( $arguments );
29
+ }
30
+ }
verbs/get-wordpress-settings.php ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-wordpress-settngs verb.
5
+ Written by Glenn Ansley iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2017-04-01 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Wordpress_Settings extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-wordpress-settings';
16
+ public static $description = 'Retrieve Settings from the WordPress options system along with supporting data from various WP functions.';
17
+ public static $status_element_name = 'wp-settings';
18
+ public static $show_in_status_by_default = false;
19
+
20
+ private $default_arguments = array();
21
+
22
+ private $response = array();
23
+
24
+ public function run( $arguments ) {
25
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
26
+
27
+ $this->set_versions();
28
+ $this->set_options();
29
+ $this->set_user_roles();
30
+ $this->set_timezone_format();
31
+ $this->set_timezone_format_string();
32
+ $this->set_timezone_options();
33
+ $this->set_date_format_options();
34
+ $this->set_time_format_options();
35
+ $this->set_day_of_week_options();
36
+ $this->set_default_category_options();
37
+ $this->set_post_format_strings();
38
+ $this->set_page_on_front_select_options();
39
+ $this->set_page_for_posts_select_options();
40
+
41
+ return $this->response;
42
+ }
43
+
44
+ /**
45
+ * Populates version data for various techs
46
+ */
47
+ private function set_versions() {
48
+ $this->response['versions'] = array(
49
+ 'wp' => $GLOBALS['wp_version'],
50
+ 'db' => get_option( 'db_version' ),
51
+ 'db-initial' => get_option( 'initial_db_version' ),
52
+ 'php' => phpversion(),
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Options we need
58
+ */
59
+ private function set_options() {
60
+ $this->response['options'] = array(
61
+ 'is_multisite' => defined('MULTISITE' ) && MULTISITE === true,
62
+ // General
63
+ 'blogname' => get_option( 'blogname' ),
64
+ 'blogdescription' => get_option( 'blogdescription' ),
65
+ 'admin_email' => get_option( 'admin_email' ),
66
+ 'users_can_register' => get_option( 'users_can_register' ),
67
+ 'default_role' => get_option( 'default_role' ),
68
+ 'date_format' => get_option( 'date_format' ),
69
+ 'time_format' => get_option( 'time_format' ),
70
+ 'start_of_week' => get_option( 'start_of_week' ),
71
+ // Writing
72
+ 'use_smilies' => get_option( 'use_smilies' ),
73
+ 'use_balanceTags' => get_option( 'use_balanceTags' ),
74
+ 'default_category' => get_option( 'default_category' ),
75
+ 'default_post_format' => get_option( 'default_post_format'),
76
+ 'link_manager_enabled' => get_option( 'link_manager_enabled' ),
77
+ 'default_link_category' => get_option( 'default_link_category' ),
78
+ 'blog_public' => get_option( 'blog_public' ),
79
+ 'ping_sites' => get_option( 'ping_sites' ),
80
+ // Reading
81
+ 'show_on_front' => get_option( 'show_on_front' ),
82
+ 'posts_per_page' => get_option( 'posts_per_page' ),
83
+ 'posts_per_rss' => get_option( 'posts_per_rss' ),
84
+ 'rss_use_excerpt' => get_option( 'rss_use_excerpt' ),
85
+ 'blog_public' => get_option( 'blog_public' ),
86
+ // Reading
87
+ 'default_pingback_flag' => get_option( 'default_pingback_flag' ),
88
+ 'default_ping_status' => get_option( 'default_ping_status' ),
89
+ 'default_comment_status' => get_option( 'default_comment_status' ),
90
+ 'require_name_email' => get_option( 'require_name_email' ),
91
+ 'comment_registration' => get_option( 'comment_registration' ),
92
+ 'close_comments_for_old_posts' => get_option( 'close_comments_for_old_posts' ),
93
+ 'close_comments_days_old' => get_option( 'close_comments_days_old' ),
94
+ 'thread_comments' => get_option( 'thread_comments' ),
95
+ 'thread_comments_depth' => get_option( 'thread_comments_depth' ),
96
+ 'page_comments' => get_option( 'page_comments' ),
97
+ 'comments_per_page' => get_option( 'comments_per_page' ),
98
+ 'default_comments_page' => get_option( 'default_comments_page' ),
99
+ 'comment_order' => get_option( 'comment_order' ),
100
+ 'comment_max_links' => get_option( 'comment_max_links' ),
101
+ 'comments_notify' => get_option( 'comments_notify' ),
102
+ 'moderation_notify' => get_option( 'moderation_notify' ),
103
+ 'comment_moderation' => get_option( 'comment_moderation' ),
104
+ 'comment_whitelist' => get_option( 'comment_whitelist' ),
105
+ 'show_avatars' => get_option( 'show_avatars' ),
106
+ 'avatar_rating' => get_option( 'avatar_rating' ),
107
+ 'avatar_default' => get_option( 'avatar_default' ),
108
+ // Media
109
+ 'thumbnail_size_w' => get_option( 'thumbnail_size_w' ),
110
+ 'thumbnail_size_h' => get_option( 'thumbnail_size_h' ),
111
+ 'thumbnail_crop' => get_option( 'thumbnail_crop' ),
112
+ 'medium_size_w' => get_option( 'medium_size_w' ),
113
+ 'medium_size_h' => get_option( 'medium_size_h' ),
114
+ 'large_size_w' => get_option( 'large_size_w' ),
115
+ 'large_size_h' => get_option( 'large_size_h' ),
116
+ 'uploads_use_yearmonth_folders' => get_option( 'uploads_use_yearmonth_folders' ),
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Return a list of user roles.
122
+ *
123
+ * slug => Display Name
124
+ *
125
+ * @since 1.0.0
126
+ * @return array
127
+ */
128
+ private function set_user_roles() {
129
+ require_once( ABSPATH . 'wp-admin/includes/user.php' );
130
+ $roles = get_editable_roles();
131
+ foreach( $roles as $i => $d ) {
132
+ $user_roles[$i] = $d['name'];
133
+ }
134
+ $this->response['user_roles'] = empty( $user_roles ) ? array() : $user_roles;
135
+ }
136
+
137
+ /**
138
+ * Set the timezone format
139
+ */
140
+ private function set_timezone_format() {
141
+ $this->response['timezone']['format'] = _x( 'Y-m-d H:i:s', 'timezone date format' );
142
+ }
143
+
144
+ /**
145
+ * Set the timezone format string
146
+ */
147
+ private function set_timezone_format_string() {
148
+ $this->response['timezone']['format_string'] = date_i18n( $this->response['timezone']['format'] );
149
+ }
150
+
151
+ /**
152
+ * Generate timezone select options
153
+ */
154
+ private function set_timezone_options() {
155
+
156
+ $current_offset = get_option('gmt_offset');
157
+ $tzstring = get_option('timezone_string');
158
+ $check_zone_info = true;
159
+
160
+ // Remove old Etc mappings. Fallback to gmt_offset.
161
+ if ( false !== strpos( $tzstring, 'Etc/GMT' ) ) {
162
+ $tzstring = '';
163
+ }
164
+
165
+ if ( empty( $tzstring ) ) { // Create a UTC+- zone if no timezone string exists
166
+ $check_zone_info = false;
167
+ if ( 0 == $current_offset ) {
168
+ $tzstring = 'UTC+0';
169
+ } else if ( $current_offset < 0 ) {
170
+ $tzstring = 'UTC' . $current_offset;
171
+ } else {
172
+ $tzstring = 'UTC+' . $current_offset;
173
+ }
174
+ }
175
+
176
+ $this->response['timezone']['options'] = explode("\n", wp_timezone_choice( $tzstring, get_user_locale() ));
177
+ }
178
+
179
+ /**
180
+ * Generate date format select options
181
+ */
182
+ private function set_date_format_options() {
183
+ $custom = true;
184
+ $formats = array_unique( apply_filters( 'date_formats', array( __( 'F j, Y' ), 'Y-m-d', 'm/d/Y', 'd/m/Y' ) ) );
185
+ foreach ( $formats as $format ) {
186
+ $date_formats[$format] = date_i18n( $format );
187
+ $custom = ( $format === $this->response['options']['date_format'] ) ? false : $custom;
188
+ }
189
+ $this->response['date_format_options'] = $date_formats;
190
+ $this->response['date_format_custom_selected'] = $custom;
191
+ }
192
+
193
+ /**
194
+ * Generate time format select options
195
+ */
196
+ private function set_time_format_options() {
197
+ $custom = true;
198
+ $formats = array_unique( apply_filters( 'time_formats', array( __( 'g:i a' ), 'g:i A', 'H:i' ) ) );
199
+ foreach ( $formats as $format ) {
200
+ $time_formats[$format] = date_i18n( $format );
201
+ $custom = ( $format === $this->response['options']['time_format'] ) ? false : $custom;
202
+ }
203
+ $this->response['time_format_options'] = $time_formats;
204
+ $this->response['time_format_custom_selected'] = $custom;
205
+ }
206
+
207
+ /**
208
+ * Generate day of week select options
209
+ */
210
+ private function set_day_of_week_options() {
211
+ global $wp_locale;
212
+ $options = array();
213
+
214
+ for ($day_index = 0; $day_index <= 6; $day_index++) :
215
+ $selected = ( get_option( 'start_of_week' ) == $day_index ) ? 'selected="selected"' : '';
216
+ $options[] = "\n\t<option value='" . esc_attr( $day_index ) . "' $selected>" . $wp_locale->get_weekday( $day_index ) . '</option>';
217
+ endfor;
218
+ $this->response['day_of_week_options'] = $options;
219
+ }
220
+
221
+ /* Generate Default Category select options
222
+ *
223
+ */
224
+ private function set_default_category_options() {
225
+ $this->response['default_category_options'] = wp_dropdown_categories(array('hide_empty' => 0, 'name' => 'default_category', 'orderby' => 'name', 'selected' => get_option('default_category'), 'hierarchical' => true));
226
+ }
227
+
228
+ /**
229
+ * All the Post Format strings in an array
230
+ */
231
+ private function set_post_format_strings() {
232
+ $this->response['post_format_strings'] = get_post_format_strings();
233
+ }
234
+
235
+ /**
236
+ * Generates page list options
237
+ */
238
+ private function set_page_on_front_select_options() {
239
+ $this->response['page_on_front_options'] = wp_dropdown_pages( array( 'name' => 'page_on_front', 'echo' => 0, 'show_option_none' => __( '&mdash; Select &mdash;' ), 'option_none_value' => '0', 'selected' => get_option( 'page_on_front' ) ) );
240
+ }
241
+
242
+ /**
243
+ * Generates page list options
244
+ */
245
+ private function set_page_for_posts_select_options() {
246
+ $this->response['page_for_posts_options'] = wp_dropdown_pages( array( 'name' => 'page_for_posts', 'echo' => 0, 'show_option_none' => __( '&mdash; Select &mdash;' ), 'option_none_value' => '0', 'selected' => get_option( 'page_for_posts' ) ) );
247
+ }
248
+ }
verbs/get-wordpress-users.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-wordpress-users verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-19 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Get_Wordpress_Users extends Ithemes_Sync_Verb {
17
+ public static $name = 'get-wordpress-users';
18
+ public static $description = 'Provides a list of WordPress users and their IDs';
19
+ public static $status_element_name = 'users';
20
+
21
+ private $default_arguments = array();
22
+
23
+
24
+ public function run( $arguments ) {
25
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
26
+
27
+ $users = Ithemes_Sync_Functions::get_users( $arguments );
28
+
29
+ return $users;
30
+ }
31
+ }
verbs/get-yoast-seo-summary.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the get-yoast-seo-summary verb.
5
+ Written by Lew Ayotte for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2016-10-05 - Lew Ayotte
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Get_Yoast_SEO_Summary extends Ithemes_Sync_Verb {
15
+ public static $name = 'get-yoast-seo-summary';
16
+ public static $description = 'Retrieve details from Yoast SEO.';
17
+
18
+ public function run( $arguments ) {
19
+ $statistics = array();
20
+ $statistics_obj = new WPSEO_Statistics();
21
+ $ranks = WPSEO_Rank::get_all_ranks();
22
+ foreach( $ranks as $rank ) {
23
+ $statistics[$rank->get_rank()] = $statistics_obj->get_post_count( $rank );
24
+ }
25
+ return $statistics;
26
+ }
27
+ }
verbs/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
verbs/manage-comments.php ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the manage-comments verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.1.0 - 2016-09-14 - Lew Ayotte
10
+ Adding Empty Spam
11
+ 1.0.0 - 2014-06-02 - Chris Jean
12
+ Initial version
13
+ */
14
+
15
+ class Ithemes_Sync_Verb_Manage_Comments extends Ithemes_Sync_Verb {
16
+ public static $name = 'manage-comments';
17
+ public static $description = 'Approve, edit, and delete comments.';
18
+
19
+ private $default_arguments = array();
20
+ private $response = array();
21
+ private $comments_cache = array();
22
+
23
+ public function run( $arguments ) {
24
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
25
+
26
+ include_once( ABSPATH . WPINC . '/comment.php' );
27
+
28
+ $actions = array(
29
+ 'add' => '',
30
+ 'edit' => '',
31
+ 'approve' => array( $this, 'approve_comment' ),
32
+ 'unapprove' => array( $this, 'unapprove_comment' ),
33
+ 'spam' => array( $this, 'spam_comment' ),
34
+ 'unspam' => array( $this, 'unspam_comment' ),
35
+ 'trash' => array( $this, 'trash_comment' ),
36
+ 'restore' => array( $this, 'restore_comment' ),
37
+ 'untrash' => array( $this, 'restore_comment' ),
38
+ 'delete' => array( $this, 'delete_comment' ),
39
+ 'emptyspam' => array( $this, 'empty_spam' ),
40
+ 'emptytrash' => array( $this, 'empty_trash' ),
41
+ );
42
+
43
+ foreach ( $arguments as $action => $data ) {
44
+ if ( 'get-actions' == $action ) {
45
+ $this->response[$action] = array_keys( $actions );
46
+ continue;
47
+ }
48
+
49
+ if ( ! isset( $actions[$action] ) ) {
50
+ $this->response[$action] = 'This action is not recognized.';
51
+ continue;
52
+ }
53
+
54
+ if ( 'add' == $action ) {
55
+ $this->response[$action] = $this->add_comment( $data );
56
+ } else if ( 'edit' == $action ) {
57
+ $this->response[$action] = $this->edit_comments( $data );
58
+ } else if ( 'emptyspam' == $action ) {
59
+ $function = $actions[$action];
60
+ if ( ! is_callable( $actions[$action] ) ) {
61
+ return new WP_Error( "missing-function-$function", "Due to an unknown issue, the $function function is not available." );
62
+ }
63
+ $this->response[$action] = call_user_func( $function );
64
+ } else {
65
+ $this->response[$action] = $this->run_function_on_ids( $actions[$action], $data );
66
+ }
67
+ }
68
+
69
+ return $this->response;
70
+ }
71
+
72
+ private function is_valid_comment_id( $id ) {
73
+ if ( isset( $this->comments_cache[$id] ) ) {
74
+ return $this->comments_cache[$id];
75
+ }
76
+
77
+ $comment = get_comment( $id );
78
+
79
+ if ( empty( $comment ) || is_wp_error( $comment ) ) {
80
+ $comment = false;
81
+ } else {
82
+ $comment = true;
83
+ }
84
+
85
+ $this->comments_cache[$id] = $comment;
86
+
87
+ return $comment;
88
+ }
89
+
90
+ private function run_function_on_ids( $function, $ids ) {
91
+ if ( is_array( $function ) ) {
92
+ if ( 'delete_comment' == $function[1] ) {
93
+ $check_function = 'wp_delete_comment';
94
+ } else {
95
+ $check_function = 'wp_set_comment_status';
96
+ }
97
+ } else {
98
+ $check_function = $function;
99
+ }
100
+
101
+ if ( ! is_callable( $check_function ) ) {
102
+ return new WP_Error( "missing-function-$check_function", "Due to an unknown issue, the $check_function function is not available." );
103
+ }
104
+
105
+ $response = array();
106
+
107
+ foreach ( $ids as $id ) {
108
+ if ( ! $this->is_valid_comment_id( $id ) ) {
109
+ $response[$id] = new WP_Error( 'invalid-comment-id', 'The requested comment could not be found.' );
110
+ } else {
111
+ $response[$id] = call_user_func( $function, $id );
112
+ }
113
+ }
114
+
115
+ return $response;
116
+ }
117
+
118
+ private function approve_comment( $id ) {
119
+ return wp_set_comment_status( $id, 'approve' );
120
+ }
121
+
122
+ private function unapprove_comment( $id ) {
123
+ return wp_set_comment_status( $id, 'hold' );
124
+ }
125
+
126
+ private function trash_comment( $id ) {
127
+ return wp_trash_comment( $id );
128
+ }
129
+
130
+ private function restore_comment( $id ) {
131
+ return wp_untrash_comment( $id );
132
+ }
133
+
134
+ private function delete_comment( $id ) {
135
+ return wp_delete_comment( $id, true );
136
+ }
137
+
138
+ private function spam_comment( $id ) {
139
+ return wp_spam_comment( $id );
140
+ }
141
+
142
+ private function unspam_comment( $id ) {
143
+ return wp_unspam_comment( $id );
144
+ }
145
+
146
+ private function edit_comments( $edits ) {
147
+ if ( ! is_array( $edits ) ) {
148
+ return new WP_Error( 'invalid-argument', 'This action requires an array of valid comment entries.' );
149
+ }
150
+
151
+ if ( ! is_callable( 'wp_update_comment' ) ) {
152
+ include_once( ABSPATH . WPINC . '/wp-includes/comment.php' );
153
+ }
154
+ if ( ! is_callable( 'wp_update_comment' ) ) {
155
+ return new WP_Error( 'missing-function-wp_update_comment', 'Due to an unknown issue, the wp_update_comment function is not available.' );
156
+ }
157
+
158
+
159
+ $response = array();
160
+
161
+ foreach ( $edits as $id => $data ) {
162
+ if ( (int) $id != $id ) {
163
+ $response[$id] = new WP_Error( 'invalid-index', 'Array keys for the edit argument must be integers, each representing a comment ID.' );
164
+ } else if ( ! $this->is_valid_comment_id( $id ) ) {
165
+ $response[$id] = new WP_Error( 'invalid-comment-id', 'The requested comment could not be found.' );
166
+ } else {
167
+ $data['comment_ID'] = $id;
168
+ $result = wp_update_comment( $data );
169
+
170
+ if ( is_int( $result ) ) {
171
+ $result = (bool) $result;
172
+ }
173
+
174
+ $response[$id] = $result;
175
+ }
176
+ }
177
+
178
+ return $response;
179
+ }
180
+
181
+ private function add_comment( $comment ) {
182
+ if ( ! is_array( $comment ) ) {
183
+ return new WP_Error( 'invalid-argument', 'This action requires an array of valid comment entries.' );
184
+ }
185
+
186
+ if ( ! isset( $comment['comment_post_ID'] ) ) {
187
+ $response = array();
188
+ $error_count = 0;
189
+
190
+ foreach ( $comment as $id => $data ) {
191
+ $response[$id] = $this->add_comment( $data );
192
+
193
+ if ( is_wp_error( $response[$id] ) ) {
194
+ $error_count++;
195
+ }
196
+ }
197
+
198
+ if ( count( $comment ) == $error_count ) {
199
+ return new WP_Error( 'invalid-argument', 'This action requires an array of valid comment entries.' );
200
+ }
201
+
202
+ return $response;
203
+ }
204
+
205
+ $required_indexes = array(
206
+ 'comment_author_IP',
207
+ 'comment_content',
208
+ 'comment_agent',
209
+ );
210
+
211
+ $comment_defaults = array(
212
+ 'comment_approved' => 1,
213
+ 'comment_karma' => 0,
214
+ 'comment_parent' => 0,
215
+ 'comment_type' => '',
216
+ 'filtered' => false,
217
+
218
+ 'sync_run_preprocess_comment_filter' => true, // Change to false to skip the preprocess_comment filter.
219
+ 'sync_send_comment_notifications' => true, // Change to false to disable comment notification emails.
220
+ );
221
+
222
+ // Starting here, much of the following code mirrors similar code from wp-comments-post.php and wp-includes/comment.php from WP version 3.9.1.
223
+ // Mirroring this code was the only way to reliably provide full comment functionality and flexibility while staying compatible with the WP API.
224
+
225
+ if ( ! empty( $comment['user_id'] ) ) {
226
+ $user = get_user_by( 'id', $comment['user_id'] );
227
+
228
+ if ( ! is_object( $user ) || ! is_a( $user, 'WP_User' ) || ! $user->exists() ) {
229
+ return new WP_Error( 'invalid-user-id', "A user with an ID of {$comment['user_id']} does not exist." );
230
+ }
231
+
232
+ if ( empty( $user->display_name ) ) {
233
+ $user->display_name = $user->user_login;
234
+ }
235
+
236
+ $comment['comment_author'] = wp_slash( $user->display_name );
237
+ $comment['comment_author_email'] = wp_slash( $user->user_email );
238
+ $comment['comment_author_url'] = wp_slash( $user->user_url );
239
+
240
+ kses_remove_filters();
241
+ kses_init_filters();
242
+ } else if ( isset( $comment['comment_author'] ) && isset( $comment['comment_author_email'] ) && isset( $comment['comment_author_url'] ) ) {
243
+ $comment['user_id'] = 0;
244
+ } else {
245
+ return new WP_Error( 'missing-required-commenter-data', 'Either user_id or comment_author, comment_author_email, and comment_author_url must be supplied.' );
246
+ }
247
+
248
+ $comment = array_merge( $comment_defaults, $comment );
249
+
250
+ $run_preprocess_comment_filter = $comment['sync_run_preprocess_comment_filter'];
251
+ unset( $comment['sync_run_preprocess_comment_filter'] );
252
+
253
+ $send_comment_notifications = $comment['sync_send_comment_notifications'];
254
+ unset( $comment['sync_send_comment_notifications'] );
255
+
256
+
257
+ $missing_indexes = array();
258
+
259
+ foreach ( $required_indexes as $index ) {
260
+ if ( empty( $comment[$index] ) ) {
261
+ $missing_indexes[] = $index;
262
+ }
263
+ }
264
+
265
+ if ( ! empty( $missing_indexes ) ) {
266
+ return new WP_Error( 'missing-comment-data', 'The following required indexes were missing in the comment data: ' . implode( ', ', $missing_indexes ) );
267
+ }
268
+
269
+
270
+ if ( $run_preprocess_comment_filter ) {
271
+ apply_filters( 'preprocess_comment', $comment );
272
+ }
273
+
274
+ $comment['comment_author_IP'] = preg_replace( '/[^0-9a-fA-F:., ]/', '', $comment['comment_author_IP'] );
275
+ $comment['comment_agent'] = substr( $comment['comment_agent'], 0, 254 );
276
+
277
+ $comment['comment_date'] = current_time( 'mysql' );
278
+ $comment['comment_date_gmt'] = current_time( 'mysql', 1 );
279
+
280
+ if ( ! $comment['filtered'] ) {
281
+ $comment = wp_filter_comment( $comment );
282
+ }
283
+
284
+ $id = wp_insert_comment( $comment );
285
+
286
+ if ( 0 == $id ) {
287
+ if ( ! empty( $GLOBALS['wpdb']->last_error ) ) {
288
+ $error = $GLOBALS['wpdb']->last_error;
289
+ } else {
290
+ $error = 'An unknown error prevented the comment from being added to the database.';
291
+ }
292
+
293
+ return new WP_Error( 'comment-insert-failure', $error );
294
+ }
295
+
296
+
297
+ do_action( 'comment_post', $id, $comment['comment_approved'] );
298
+
299
+ if ( $send_comment_notifications && ( 'spam' !== $comment['comment_approved'] ) ) {
300
+ if ( '0' == $comment['comment_approved'] ) {
301
+ wp_notify_moderator( $id );
302
+ }
303
+
304
+ if ( get_option( 'comments_notify' ) && $comment['comment_approved'] ) {
305
+ wp_notify_postauthor( $id );
306
+ }
307
+ }
308
+
309
+
310
+ $comment['comment_ID'] = $id;
311
+
312
+ return $comment;
313
+ }
314
+
315
+ function empty_spam() {
316
+ global $wpdb;
317
+
318
+ $comment_status = 'spam';
319
+ $delete_time = current_time( 'mysql', 1 );
320
+ $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_approved = %s AND %s > comment_date_gmt", $comment_status, $delete_time ) );
321
+
322
+ $deleted = 0;
323
+ foreach ( $comment_ids as $comment_id ) { // Check the permissions on each
324
+ wp_delete_comment( $comment_id, true );
325
+ $deleted++;
326
+ }
327
+ return $deleted;
328
+ }
329
+
330
+ function empty_trash() {
331
+ global $wpdb;
332
+
333
+ $comment_status = 'trash';
334
+ $delete_time = current_time( 'mysql', 1 );
335
+ $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_approved = %s AND %s > comment_date_gmt", $comment_status, $delete_time ) );
336
+
337
+ $deleted = 0;
338
+ foreach ( $comment_ids as $comment_id ) { // Check the permissions on each
339
+ wp_delete_comment( $comment_id, true );
340
+ $deleted++;
341
+ }
342
+ return $deleted;
343
+ }
344
+ }
verbs/manage-ithemes-licenses.php ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the manage-ithemes-licenses verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-11-18 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Manage_Ithemes_Licenses extends Ithemes_Sync_Verb {
15
+ public static $name = 'manage-ithemes-licenses';
16
+ public static $description = 'Get, set, and delete license keys for iThemes products.';
17
+ public static $status_element_name = 'ithemes-licenses';
18
+ public static $show_in_status_by_default = false;
19
+
20
+ private $default_arguments = array();
21
+ private $response = array();
22
+
23
+
24
+ public function run( $arguments ) {
25
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
26
+
27
+
28
+ if ( empty( $GLOBALS['ithemes_updater_path'] ) ) {
29
+ if ( defined( 'ITHEMES_UPDATER_DISABLE' ) && ITHEMES_UPDATER_DISABLE ) {
30
+ return new WP_Error( 'ithemes-updater-is-disabled', 'The iThemes updater library is disabled on this site due to the ITHEMES_UPDATER_DISABLE define being set to a truthy value. Licensing for this site cannot be managed.' );
31
+ } else {
32
+ return new WP_Error( 'ithemes-updater-not-loaded', 'The $GLOBALS[\'ithemes_updater_path\'] variable is empty or not set. This indicates that the updater was not loaded although the cause for this is not known.' );
33
+ }
34
+ }
35
+
36
+ include_once( $GLOBALS['ithemes_updater_path'] . '/keys.php' );
37
+
38
+ if ( ! class_exists( 'Ithemes_Updater_Keys' ) ) {
39
+ return new WP_Error( 'ithemes-updater-keys-class-missing', "The Ithemes_Updater_Keys class was not found. This could indicate an issue with the files of the updater located at {$GLOBALS['ithemes_updater_path']}." );
40
+ }
41
+
42
+
43
+ $actions = array(
44
+ 'get' => 'get_keys',
45
+ 'set' => 'set_keys',
46
+ );
47
+
48
+
49
+ if ( empty( $arguments ) ) {
50
+ $arguments = array( 'get' => true );
51
+ }
52
+
53
+
54
+ foreach ( $arguments as $action => $data ) {
55
+ if ( ! isset( $actions[$action] ) ) {
56
+ $this->response[$action] = 'This action is not recognized.';
57
+ continue;
58
+ }
59
+ if ( ! is_array( $data ) && ( 'set' == $action ) ) {
60
+ $this->response[$action] = new WP_Error( 'invalid-argument', 'This action requires an array.' );
61
+ continue;
62
+ }
63
+
64
+ $this->response[$action] = call_user_func( array( $this, $actions[$action] ), $data );
65
+ }
66
+
67
+ return $this->response;
68
+ }
69
+
70
+ private function get_keys( $packages ) {
71
+ if ( ! is_callable( 'Ithemes_Updater_Keys', 'get' ) ) {
72
+ return new WP_Error( 'ithemes-updater-keys-get-not-callable', 'The Ithemes_Updater_Keys::get function is not callable due to an unknown issue. Unable to process the request.' );
73
+ }
74
+
75
+ @include_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
76
+
77
+ if ( ! class_exists( 'Ithemes_Updater_Packages' ) ) {
78
+ return new WP_Error( 'ithemes-updater-packages-class-missing', "The Ithemes_Updater_Packages class was not found. This could indicate an issue with the files of the updater located at {$GLOBALS['ithemes_updater_path']}." );
79
+ } else if ( ! is_callable( 'Ithemes_Updater_Packages', 'get_full_details' ) ) {
80
+ return new WP_Error( 'ithemes-updater-packages-get-full-details-not-callable', 'The Ithemes_Updater_Packages::get_full_details function is not callable due to an unknown issue. Unable to process the request.' );
81
+ }
82
+
83
+ if ( empty( $packages ) ) {
84
+ $packages = '__all__';
85
+ }
86
+
87
+ $keys = Ithemes_Updater_Keys::get( $packages );
88
+
89
+ if ( '__all__' == $packages ) {
90
+ $package_details = Ithemes_Updater_Packages::get_full_details();
91
+ $all_packages = $package_details['packages'];
92
+
93
+ foreach ( $package_details['packages'] as $path => $data ) {
94
+ if ( ! isset( $keys[$data['package']] ) ) {
95
+ $keys[$data['package']] = null;
96
+ }
97
+ }
98
+ }
99
+
100
+
101
+ return $keys;
102
+ }
103
+
104
+ private function set_keys( $packages ) {
105
+ if ( ! is_callable( 'Ithemes_Updater_Keys', 'set' ) ) {
106
+ return new WP_Error( 'ithemes-updater-keys-set-not-callable', 'The Ithemes_Updater_Keys::set function is not callable due to an unknown issue. Unable to process the request.' );
107
+ }
108
+
109
+ return Ithemes_Updater_Keys::set( $packages );
110
+ }
111
+ }
verbs/manage-options.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Implementation of the manage-options verb.
5
+ * Written by Aaron D. Campbell for iThemes.com
6
+ *
7
+ * @version 1.0.0
8
+ *
9
+ * Version History
10
+ * 1.0.0 - 2015-02-04 - Aaron D. Campbell
11
+ * Initial version
12
+ */
13
+
14
+ class Ithemes_Sync_Verb_Manage_Options extends Ithemes_Sync_Verb {
15
+ public static $name = 'manage-options';
16
+ public static $description = 'Manage values from the WordPress options system.';
17
+
18
+ private $default_arguments = array();
19
+ private $response = array();
20
+
21
+ public function run( $arguments ) {
22
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
23
+
24
+ $actions = array(
25
+ 'update-options' => 'update_option',
26
+ 'add-options' => 'add_option',
27
+ 'delete-options' => 'delete_option',
28
+ 'update-site-options' => 'update_site_option',
29
+ 'add-site-options' => 'add_site_option',
30
+ 'delete-site-options' => 'delete_site_option',
31
+ 'set-transients' => 'set_transient',
32
+ 'delete-transients' => 'delete_transient',
33
+ 'set-site-transients' => 'set_site_transient',
34
+ 'delete-site-transients' => 'delete_site_transient',
35
+ );
36
+
37
+ foreach ( $arguments as $action => $data ) {
38
+ if ( 'get-actions' == $action ) {
39
+ $this->response[$action] = array_keys( $actions );
40
+ continue;
41
+ }
42
+
43
+ if ( ! isset( $actions[$action] ) ) {
44
+ $this->response[$action] = 'This action is not recognized.';
45
+ continue;
46
+ }
47
+
48
+ if ( ! is_array( $data ) ) {
49
+ $this->response[$action] = new WP_Error( 'invalid-argument', 'This action requires an array.' );
50
+ continue;
51
+ }
52
+
53
+ $this->response[$action] = $this->handle_request( $actions[$action], $data );
54
+ }
55
+
56
+ return $this->response;
57
+ }
58
+
59
+ private function handle_request( $function, $data ) {
60
+ if ( ! is_callable( $function ) ) {
61
+ return new WP_Error( "missing-function-$function", "Due to an unknown issue, the $function function is not available." );
62
+ }
63
+
64
+ $response = array();
65
+
66
+ foreach ( $data as $id => $params ) {
67
+ if ( ! is_array( $function ) ) {
68
+ $response[$id] = call_user_func_array( $function, $params );
69
+ continue;
70
+ }
71
+ }
72
+
73
+ return $response;
74
+ }
75
+ }
verbs/manage-plugins.php ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the manage-plugins verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-29 - Chris Jean
12
+ Added the install action.
13
+ 1.1.1 - 2014-01-29 - Chris Jean
14
+ Updated the uninstall action to deactivate the plugins before attempting to uninstall.
15
+ 1.2.0 - 2014-05-20 - Chris Jean
16
+ Added install-and-activate.
17
+ Added network-activate as a duplicate of network_activate.
18
+ Updated install output to include result, slug, and success details.
19
+ */
20
+
21
+
22
+ class Ithemes_Sync_Verb_Manage_Plugins extends Ithemes_Sync_Verb {
23
+ public static $name = 'manage-plugins';
24
+ public static $description = 'Activate, deactivate, and uninstall plugins.';
25
+
26
+ private $default_arguments = array();
27
+ private $handled_activation = false;
28
+ private $response = array();
29
+ private $current_action = '';
30
+
31
+
32
+ public function run( $arguments ) {
33
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
34
+
35
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
36
+
37
+
38
+ $actions = array(
39
+ 'deactivate' => 'deactivate_plugins',
40
+ 'install' => 'install_plugins',
41
+ 'uninstall' => 'uninstall_plugins',
42
+ 'install-and-activate' => 'install_and_activate_plugin',
43
+ 'activate' => 'activate_plugin',
44
+ 'network-activate' => 'network_activate_plugin',
45
+ 'network_activate' => 'network_activate_plugin',
46
+ );
47
+
48
+ if ( isset( $actions['get-actions'] ) ) {
49
+ $this->response['get-actions'] = array_keys( $actions );
50
+ }
51
+
52
+ foreach ( $arguments as $action => $data ) {
53
+ $this->current_action = $action;
54
+
55
+ if ( isset( $actions[$action] ) ) {
56
+ $this->response[$action] = call_user_func( array( $this, $actions[$action] ), $data );
57
+ } else {
58
+ $this->response[$action] = 'This action is not recognized';
59
+ }
60
+ }
61
+
62
+ return $this->response;
63
+ }
64
+
65
+ private function set_fatal_error_handler() {
66
+ if ( function_exists( 'error_get_last' ) ) {
67
+ register_shutdown_function( array( $this, 'handle_fatal_error' ) );
68
+ }
69
+ }
70
+
71
+ private function handle_fatal_error() {
72
+ $error = error_get_last();
73
+
74
+ if ( is_array( $error ) ) {
75
+ $this->response['error'] = array(
76
+ 'error_trigger_action' => $this->current_action,
77
+ 'error_details' => $error,
78
+ );
79
+
80
+ $GLOBALS['ithemes_sync_request_handler']->send_response( $this->response );
81
+ }
82
+ }
83
+
84
+ private function install_and_activate_plugin( $plugin ) {
85
+ if ( ! is_string( $plugin ) ) {
86
+ return new WP_Error( 'invalid-argument', 'The install-and-activate argument takes a string representing an individual plugin.' );
87
+ }
88
+
89
+ $result['install'] = $this->install_plugins( array( $plugin ) );
90
+
91
+ if ( isset( $result['install'][$plugin] ) ) {
92
+ $result['install'] = $result['install'][$plugin];
93
+ }
94
+
95
+ $this->response['install-and-activate'] = $result;
96
+
97
+ if ( ! empty( $result['install']['slug'] ) ) {
98
+ $result['activate'] = $this->activate_plugin( $result['install']['slug'] );
99
+ } else {
100
+ $result['activate'] = new WP_Error( 'skip-activate', 'Unable to activate due to failed install.' );
101
+ }
102
+
103
+ if ( ! is_wp_error( $result['activate'] ) ) {
104
+ $result['success'] = true;
105
+ }
106
+
107
+ return $result;
108
+ }
109
+
110
+ private function activate_plugin( $plugin ) {
111
+ if ( $this->handled_activation ) {
112
+ return new WP_Error( 'already-handled-activation', 'Another activation has already been handled in this request. Only one activation per request is supported.' );
113
+ }
114
+
115
+ if ( ! is_string( $plugin ) ) {
116
+ return new WP_Error( 'invalid-argument', 'The activate argument only accepts a string representing a single plugin.' );
117
+ }
118
+
119
+ $this->handled_activation = true;
120
+
121
+ $result = activate_plugin( $plugin );
122
+ if ( is_wp_error( $result ) ) {
123
+ return $result;
124
+ } else {
125
+ $result['data'] = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, true, false );
126
+ return $result;
127
+ }
128
+ }
129
+
130
+ private function network_activate_plugin( $plugin ) {
131
+ if ( $this->handled_activation ) {
132
+ return new WP_Error( 'already-handled-activation', 'Another activation has already been handled in this request. Only one activation per request is supported.' );
133
+ }
134
+
135
+ if ( ! is_string( $plugin ) ) {
136
+ return new WP_Error( 'invalid-argument', 'The network_activate argument only accepts a string representing a single plugin.' );
137
+ }
138
+
139
+ $this->handled_activation = true;
140
+
141
+ $result = activate_plugin( $plugin, '', true );
142
+ if ( is_wp_error( $result ) ) {
143
+ return $result;
144
+ } else {
145
+ $result['data'] = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, true, false );
146
+ return $result;
147
+ }
148
+ }
149
+
150
+ private function deactivate_plugins( $plugin ) { //We only send one at a time
151
+ $result['data'] = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, true, false );
152
+ deactivate_plugins( (array) $plugin );
153
+ return $result;
154
+ }
155
+
156
+ private function install_plugins( $plugins ) {
157
+ require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
158
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
159
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
160
+ require_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );
161
+ require_once( $GLOBALS['ithemes_sync_path'] . '/upgrader-skin.php' );
162
+
163
+ $upgrader = new Plugin_Upgrader( new Ithemes_Sync_Upgrader_Skin() );
164
+
165
+ $results = array();
166
+
167
+
168
+ foreach ( (array) $plugins as $plugin ) {
169
+ Ithemes_Sync_Functions::set_time_limit( 300 );
170
+
171
+ if ( preg_match( '{^(http|https|ftp)://}i', $plugin ) ) {
172
+ $result = $upgrader->install( $plugin );
173
+ } else {
174
+ $api = plugins_api( 'plugin_information', array( 'slug' => $plugin, 'fields' => array( 'sections' => false ) ) );
175
+
176
+ if ( is_wp_error( $api ) ) {
177
+ $result = $api;
178
+ } else {
179
+ $result = $upgrader->install( $api->download_link );
180
+ }
181
+ }
182
+
183
+ if ( is_wp_error( $result ) ) {
184
+ $results[$plugin]['error'] = array(
185
+ 'error_code' => $result->get_error_code(),
186
+ 'error_details' => $result->get_error_message(),
187
+ );
188
+ } else {
189
+ $results[$plugin] = array(
190
+ 'result' => $result,
191
+ 'slug' => $upgrader->plugin_info(),
192
+ );
193
+
194
+ if ( true === $result ) {
195
+ $results[$plugin]['success'] = true;
196
+ }
197
+ }
198
+ }
199
+
200
+ Ithemes_Sync_Functions::refresh_plugin_updates();
201
+
202
+
203
+ return $results;
204
+ }
205
+
206
+ private function uninstall_plugins( $plugins ) {
207
+ require_once( ABSPATH . '/wp-admin/includes/file.php' );
208
+
209
+ // First ensure that the plugins are deactivated.
210
+ $result = $this->deactivate_plugins( $plugins );
211
+ delete_plugins( (array) $plugins );
212
+
213
+ return $result;
214
+ }
215
+ }
verbs/manage-posts.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the manage-posts verb.
5
+ Written by Lew Ayotte for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2016-10-27 - Lew Ayotte
10
+ Initial version
11
+ */
12
+
13
+ class Ithemes_Sync_Verb_Manage_Posts extends Ithemes_Sync_Verb {
14
+ public static $name = 'manage-posts';
15
+ public static $description = 'WordPress post verbs.';
16
+
17
+ private $default_arguments = array();
18
+ private $response = array();
19
+ private $posts_cache = array();
20
+
21
+ public function run( $arguments ) {
22
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
23
+
24
+ $actions = array(
25
+ 'add' => '',
26
+ 'edit' => '',
27
+ 'trash' => array( $this, 'trash_post' ),
28
+ 'restore' => array( $this, 'restore_post' ),
29
+ 'untrash' => array( $this, 'restore_post' ),
30
+ 'delete' => array( $this, 'delete_post' ),
31
+ 'emptytrash' => array( $this, 'empty_trash' ),
32
+ 'deletepostrevisions' => array( $this, 'delete_revisions' ),
33
+ );
34
+
35
+ foreach ( $arguments as $action => $data ) {
36
+ if ( 'get-actions' == $action ) {
37
+ $this->response[$action] = array_keys( $actions );
38
+ continue;
39
+ }
40
+
41
+ if ( ! isset( $actions[$action] ) ) {
42
+ $this->response[$action] = 'This action is not recognized.';
43
+ continue;
44
+ }
45
+ if ( in_array( $action, array( 'deletepostrevisions', 'emptytrash' ) ) ) {
46
+ $function = $actions[$action];
47
+ if ( ! is_callable( $actions[$action] ) ) {
48
+ return new WP_Error( "missing-function-$function", "Due to an unknown issue, the $function function is not available." );
49
+ }
50
+ $this->response[$action] = call_user_func( $function, $data );
51
+ } else {
52
+ $this->response[$action] = $this->run_function_on_ids( $actions[$action], $data );
53
+ }
54
+ }
55
+
56
+ return $this->response;
57
+ }
58
+
59
+ private function is_valid_post_id( $id ) {
60
+ if ( isset( $this->posts_cache[$id] ) ) {
61
+ return $this->posts_cache[$id];
62
+ }
63
+
64
+ $post = get_post( $id );
65
+
66
+ if ( empty( $post ) || is_wp_error( $post ) ) {
67
+ $post = false;
68
+ } else {
69
+ $post = true;
70
+ }
71
+
72
+ $this->posts_cache[$id] = $post;
73
+
74
+ return $post;
75
+ }
76
+
77
+ private function run_function_on_ids( $function, $ids ) {
78
+ if ( ! is_callable( $function ) ) {
79
+ return new WP_Error( "missing-function-$function", "Due to an unknown issue, the $function function is not available." );
80
+ }
81
+
82
+ $response = array();
83
+
84
+ foreach ( $ids as $id ) {
85
+ if ( ! $this->is_valid_post_id( $id ) ) {
86
+ $response[$id] = new WP_Error( 'invalid-post-id', 'The requested post could not be found.' );
87
+ } else {
88
+ $response[$id] = call_user_func( $function, $id );
89
+ }
90
+ }
91
+
92
+ return $response;
93
+ }
94
+
95
+ private function trash_post( $id ) {
96
+ return wp_trash_post( $id );
97
+ }
98
+
99
+ private function restore_post( $id ) {
100
+ return wp_untrash_post( $id );
101
+ }
102
+
103
+ private function delete_post( $id ) {
104
+ return wp_delete_post( $id, true );
105
+ }
106
+
107
+ private function empty_trash( $post_type ) {
108
+ global $wpdb;
109
+
110
+ $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type=%s AND post_status = 'trash'", $post_type ) );
111
+
112
+ $deleted = 0;
113
+ foreach ( $post_ids as $post_id ) { // Check the permissions on each
114
+ wp_delete_post( $post_id, true );
115
+ $deleted++;
116
+ }
117
+ return $deleted;
118
+ }
119
+
120
+ /**
121
+ * Deletes ALL posts with post_type 'revision' and returns number deleted
122
+ */
123
+ private function delete_revisions() {
124
+ global $wpdb;
125
+
126
+ $post_ids = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_type='revision'" );
127
+
128
+ $deleted = 0;
129
+ foreach ( $post_ids as $post_id ) {
130
+ wp_delete_post( $post_id, true );
131
+ $deleted++;
132
+ }
133
+ return $deleted;
134
+ }
135
+
136
+ }
verbs/manage-reports.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the manage-reports verb.
5
+ Written by Lew Ayotte for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2016-07-20 - Lew Ayotte
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Manage_Reports extends Ithemes_Sync_Verb {
15
+ public static $name = 'manage-reports';
16
+ public static $description = 'Import Site Reports.';
17
+
18
+ private $default_arguments = array();
19
+ private $handled_activation = false;
20
+ private $response = array();
21
+ private $current_action = '';
22
+
23
+
24
+ public function run( $arguments ) {
25
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
26
+
27
+ $actions = array(
28
+ 'download-report' => 'download_report',
29
+ 'delete-report' => 'delete_report',
30
+ );
31
+
32
+ if ( isset( $actions['get-actions'] ) ) {
33
+ $this->response['get-actions'] = array_keys( $actions );
34
+ }
35
+
36
+ foreach ( $arguments as $action => $data ) {
37
+ $this->current_action = $action;
38
+
39
+ if ( isset( $actions[$action] ) ) {
40
+ $this->response[$action] = call_user_func( array( $this, $actions[$action] ), $data );
41
+ } else {
42
+ $this->response[$action] = 'This action is not recognized';
43
+ }
44
+ }
45
+
46
+ return $this->response;
47
+ }
48
+
49
+ private function set_fatal_error_handler() {
50
+ if ( function_exists( 'error_get_last' ) ) {
51
+ register_shutdown_function( array( $this, 'handle_fatal_error' ) );
52
+ }
53
+ }
54
+
55
+ private function handle_fatal_error() {
56
+ $error = error_get_last();
57
+
58
+ if ( is_array( $error ) ) {
59
+ $this->response['error'] = array(
60
+ 'error_trigger_action' => $this->current_action,
61
+ 'error_details' => $error,
62
+ );
63
+
64
+ $GLOBALS['ithemes_sync_request_handler']->send_response( $this->response );
65
+ }
66
+ }
67
+
68
+ private function download_report( $dest_filename ) {
69
+ if ( ! is_string( $dest_filename ) ) {
70
+ return new WP_Error( 'invalid-argument', 'The download-report function takes a string representing an individual report.' );
71
+ }
72
+
73
+ $report_url = 'https://s3.amazonaws.com/sync-reports.ithemes.com/' . $dest_filename;
74
+ $upload_path = Ithemes_Sync_Functions::get_upload_reports_dir();
75
+ $result = false;
76
+
77
+ if ( wp_is_writable( $upload_path ) ) {
78
+ if ( ! file_exists( $upload_path . '/index.php' ) ) {
79
+ @file_put_contents( $upload_path . '/index.php', '<?php' . PHP_EOL . '// Silence is golden.' );
80
+ }
81
+ $response = wp_remote_get( $report_url );
82
+ if ( ! is_wp_error( $response ) && 200 == wp_remote_retrieve_response_code( $response ) ) {
83
+ $contents = wp_remote_retrieve_body( $response );
84
+ @file_put_contents( $upload_path . '/' . $dest_filename, $contents );
85
+ $result = Ithemes_Sync_Functions::get_upload_reports_url() . '/' . $dest_filename;
86
+ }
87
+ }
88
+
89
+ return $result;
90
+ }
91
+
92
+ private function delete_report( $filename ) {
93
+ if ( !empty( $filename ) && !is_string( $filename ) ) {
94
+ return new WP_Error( 'invalid-argument', 'The delete-report function takes a string representing an individual report.' );
95
+ }
96
+
97
+ $upload_path = Ithemes_Sync_Functions::get_upload_reports_dir();
98
+
99
+ if ( !empty( $upload_path ) && wp_is_writable( $upload_path ) ) {
100
+ @unlink( $upload_path . '/' . $filename );
101
+ return true;
102
+ }
103
+
104
+ return false;
105
+ }
106
+
107
+ }
verbs/manage-roles.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the manage-roles verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2014-06-02 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Manage_Roles extends Ithemes_Sync_Verb {
15
+ public static $name = 'manage-roles';
16
+ public static $description = 'Create, edit, and delete roles.';
17
+
18
+ private $default_arguments = array();
19
+ private $response = array();
20
+ private $role_cache = array();
21
+
22
+
23
+ public function run( $arguments ) {
24
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
25
+
26
+
27
+ $actions = array(
28
+ 'add' => 'add_role',
29
+ 'add-cap' => array( $this, 'add_cap' ),
30
+ 'remove-cap' => array( $this, 'remove_cap' ),
31
+ 'delete' => 'remove_role',
32
+ );
33
+
34
+ foreach ( $arguments as $action => $data ) {
35
+ if ( 'get-actions' == $action ) {
36
+ $this->response[$action] = array_keys( $actions );
37
+ continue;
38
+ }
39
+
40
+ if ( ! isset( $actions[$action] ) ) {
41
+ $this->response[$action] = 'This action is not recognized.';
42
+ continue;
43
+ }
44
+ if ( ! is_array( $data ) ) {
45
+ $this->response[$action] = new WP_Error( 'invalid-argument', 'This action requires an array.' );
46
+ continue;
47
+ }
48
+
49
+ $this->response[$action] = $this->run_function( $actions[$action], $data );
50
+ }
51
+
52
+ return $this->response;
53
+ }
54
+
55
+ private function run_function( $function, $data ) {
56
+ if ( ! is_array( $function ) && ! is_callable( $function ) ) {
57
+ return new WP_Error( "missing-function-$function", "Due to an unknown issue, the $function function is not available." );
58
+ }
59
+
60
+
61
+ $response = array();
62
+
63
+ foreach ( $data as $id => $params ) {
64
+ if ( ! is_array( $function ) ) {
65
+ $response[$id] = call_user_func_array( $function, $params );
66
+ continue;
67
+ }
68
+
69
+
70
+ $function = $function[1];
71
+
72
+ if ( isset( $this->role_cache[$id] ) ) {
73
+ $role = $this->role_cache[$id];
74
+ } else {
75
+ $role = get_role( $id );
76
+ $this->role_cache[$id] = $role;
77
+ }
78
+
79
+ if ( ! is_object( $role ) || ! is_a( $role, 'WP_Role' ) ) {
80
+ $response[$id] = new WP_Error( 'invalid-role-name', "Unable to find the requested role with the name of $id." );
81
+ continue;
82
+ }
83
+
84
+ if ( ! is_callable( array( $role, $function ) ) ) {
85
+ $response[$id] = new WP_Error( "missing-function-role-$function", "Due to an unknown issue, the \$role->$function function is not available." );
86
+ continue;
87
+ }
88
+
89
+ if ( ! is_array( $params[0] ) ) {
90
+ $response[$id] = call_user_func_array( array( $role, $function ), $params );
91
+ } else {
92
+ foreach ( $params as $index => $param ) {
93
+ $response[$id][$index] = call_user_func_array( array( $role, $function ), $param );
94
+ }
95
+ }
96
+ }
97
+
98
+ return $response;
99
+ }
100
+ }
verbs/manage-themes.php ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the manage-themes verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.2.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-29 - Chris Jean
12
+ Added the install action.
13
+ 1.2.0 - 2014-05-20 - Chris Jean
14
+ Added install-and-activate.
15
+ Updated install output to include result, slug, and success details.
16
+ */
17
+
18
+
19
+ class Ithemes_Sync_Verb_Manage_themes extends Ithemes_Sync_Verb {
20
+ public static $name = 'manage-themes';
21
+ public static $description = 'Activate, deactivate, and uninstall themes.';
22
+
23
+ private $default_arguments = array();
24
+
25
+
26
+ public function run( $arguments ) {
27
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
28
+
29
+
30
+ $response = array();
31
+
32
+ $actions = array(
33
+ 'get-enabled-multisite' => 'get_enabled_themes',
34
+ 'enable-multisite' => 'enable_themes',
35
+ 'disable-multisite' => 'disable_themes',
36
+ 'install' => 'install_themes',
37
+ 'uninstall' => 'uninstall_themes',
38
+ 'install-and-activate' => 'install_and_activate_theme',
39
+ 'activate' => 'activate_theme',
40
+ );
41
+
42
+ foreach ( $arguments as $action => $data ) {
43
+ if ( isset( $actions[$action] ) ) {
44
+ $response[$action] = call_user_func( array( $this, $actions[$action] ), $data );
45
+ } else {
46
+ $response[$action] = 'This action is not recognized';
47
+ }
48
+ }
49
+
50
+ if ( isset( $actions['get-actions'] ) ) {
51
+ $response['get-actions'] = array_keys( $actions );
52
+ }
53
+
54
+
55
+ return $response;
56
+ }
57
+
58
+ private function install_and_activate_theme( $theme ) {
59
+ if ( ! is_string( $theme ) ) {
60
+ return new WP_Error( 'invalid-argument', 'The install-and-activate argument takes a string representing an individual theme.' );
61
+ }
62
+
63
+ $result['install'] = $this->install_themes( array( $theme ) );
64
+
65
+ if ( isset( $result['install'][$theme] ) ) {
66
+ $result['install'] = $result['install'][$theme];
67
+ }
68
+
69
+ $this->response['install-and-activate'] = $result;
70
+
71
+ if ( ! empty( $result['install']['slug'] ) ) {
72
+ $result['activate'] = $this->activate_theme( $result['install']['slug'] );
73
+ } else {
74
+ $result['activate'] = new WP_Error( 'skip-activate', 'Unable to activate due to failed install.' );
75
+ }
76
+
77
+ if ( ! is_wp_error( $result['activate'] ) ) {
78
+ $result['success'] = true;
79
+ }
80
+
81
+ return $result;
82
+ }
83
+
84
+ private function activate_theme( $theme ) {
85
+ if ( ! is_string( $theme ) ) {
86
+ return new WP_Error( 'invalid-argument', 'The activate argument only accepts a string representing a single theme.' );
87
+ }
88
+ switch_theme( $theme );
89
+ $new_theme = wp_get_theme( $theme );
90
+ $result['data']['name'] = $new_theme->get( 'Name' );
91
+ $result['data']['version'] = $new_theme->get( 'Version' );
92
+ return $result;
93
+ }
94
+
95
+ private function get_enabled_themes() {
96
+ return get_site_option( 'allowedthemes' );
97
+ }
98
+
99
+ private function enable_themes( $themes ) {
100
+ $allowed_themes = get_site_option( 'allowedthemes' );
101
+
102
+ foreach ( (array) $themes as $theme ) {
103
+ $allowed_themes[$theme] = true;
104
+ }
105
+
106
+ update_site_option( 'allowedthemes', $allowed_themes );
107
+
108
+ return true;
109
+ }
110
+
111
+ private function disable_themes( $themes ) {
112
+ $allowed_themes = get_site_option( 'allowedthemes' );
113
+
114
+ foreach ( (array) $themes as $theme ) {
115
+ unset( $allowed_themes[$theme] );
116
+ }
117
+
118
+ update_site_option( 'allowedthemes', $allowed_themes );
119
+
120
+ return true;
121
+ }
122
+
123
+ private function install_themes( $themes ) {
124
+ require_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
125
+ require_once( ABSPATH . 'wp-admin/includes/file.php' );
126
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
127
+ require_once( ABSPATH . 'wp-admin/includes/theme.php' );
128
+ require_once( $GLOBALS['ithemes_sync_path'] . '/upgrader-skin.php' );
129
+
130
+ $upgrader = new Theme_Upgrader( new Ithemes_Sync_Upgrader_Skin() );
131
+
132
+ $results = array();
133
+
134
+
135
+ foreach ( (array) $themes as $theme ) {
136
+ Ithemes_Sync_Functions::set_time_limit( 300 );
137
+
138
+ if ( preg_match( '{^(http|https|ftp)://}i', $theme ) ) {
139
+ $result = $upgrader->install( $theme );
140
+ } else {
141
+ $api = themes_api( 'theme_information', array( 'slug' => $theme, 'fields' => array( 'sections' => false, 'tags' => false ) ) );
142
+ if ( is_wp_error( $api ) ) {
143
+ $result = $api;
144
+ } else {
145
+ $result = $upgrader->install( $api->download_link );
146
+ }
147
+ }
148
+
149
+ if ( is_wp_error( $result ) ) {
150
+ $results[$theme]['error'] = array(
151
+ 'error_code' => $result->get_error_code(),
152
+ 'error_details' => $result->get_error_message(),
153
+ );
154
+ } else {
155
+ $results[$theme]['result'] = $result;
156
+
157
+ $theme_info = $upgrader->theme_info();
158
+ $results[$theme]['name'] = $theme_info->get( 'Name' );
159
+ $results[$theme]['version'] = $theme_info->get( 'Version' );
160
+ if ( is_object( $theme_info ) && is_callable( array( $theme_info, 'get_stylesheet' ) ) ) {
161
+ $results[$theme]['slug'] = basename( $theme_info->get_stylesheet() );
162
+ } else if ( isset( $upgrader->result ) && ! empty( $upgrader->result['destination_name'] ) ) {
163
+ $results[$theme]['slug'] = $upgrader->result['destination_name'];
164
+ }
165
+
166
+ if ( true === $result ) {
167
+ $results[$theme]['success'] = true;
168
+ }
169
+ }
170
+ }
171
+
172
+ Ithemes_Sync_Functions::refresh_theme_updates();
173
+
174
+ return $results;
175
+ }
176
+
177
+ private function uninstall_themes( $themes ) {
178
+ require_once( ABSPATH . '/wp-admin/includes/file.php' );
179
+ require_once( ABSPATH . '/wp-admin/includes/theme.php' );
180
+
181
+ $response = array();
182
+
183
+ foreach ( (array) $themes as $theme ) {
184
+ $response[$theme] = delete_theme( $theme );
185
+ }
186
+
187
+ return $response;
188
+ }
189
+ }
verbs/manage-users.php ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the manage-users verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.1 - 2014-08-01 - Aaron D. Campbell
10
+ Add get-meta, set-meta, and delete-meta actions
11
+ 1.0.0 - 2014-06-02 - Chris Jean
12
+ Initial version
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb_Manage_Users extends Ithemes_Sync_Verb {
17
+ public static $name = 'manage-users';
18
+ public static $description = 'Create, edit, and delete users.';
19
+
20
+ private $default_arguments = array();
21
+ private $response = array();
22
+ private $user_cache = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ require_once( ABSPATH . 'wp-admin/includes/user.php' );
29
+
30
+
31
+ $actions = array(
32
+ 'add' => 'wp_insert_user',
33
+ 'edit' => 'wp_update_user',
34
+ 'set-role' => array( 'user', 'set_role' ),
35
+ 'add-role' => array( 'user', 'add_role' ),
36
+ 'remove-role' => array( 'user', 'remove_role' ),
37
+ 'add-cap' => array( 'user', 'add_cap' ),
38
+ 'remove-cap' => array( 'user', 'remove_cap' ),
39
+ 'get-meta' => 'get_user_meta',
40
+ 'set-meta' => 'update_user_meta',
41
+ 'remove-meta' => 'delete_user_meta',
42
+ 'delete' => 'wp_delete_user',
43
+ );
44
+
45
+ foreach ( $arguments as $action => $data ) {
46
+ if ( 'get-actions' == $action ) {
47
+ $this->response[$action] = array_keys( $actions );
48
+ continue;
49
+ }
50
+
51
+ if ( ! isset( $actions[$action] ) ) {
52
+ $this->response[$action] = 'This action is not recognized.';
53
+ continue;
54
+ }
55
+ if ( ! is_array( $data ) ) {
56
+ $this->response[$action] = new WP_Error( 'invalid-argument', 'This action requires an array.' );
57
+ continue;
58
+ }
59
+
60
+ $this->response[$action] = $this->run_function( $actions[$action], $data );
61
+ }
62
+
63
+ return $this->response;
64
+ }
65
+
66
+ private function run_function( $function, $data ) {
67
+ if ( ! is_array( $function ) && ! is_callable( $function ) ) {
68
+ return new WP_Error( "missing-function-$function", "Due to an unknown issue, the $function function is not available." );
69
+ }
70
+
71
+
72
+ $response = array();
73
+ $update_user_level = false;
74
+
75
+ if ( is_array( $function ) && preg_match( '/_cap$/', $function[1] ) ) {
76
+ $update_user_level = true;
77
+ }
78
+
79
+ foreach ( $data as $id => $params ) {
80
+ if ( ! is_array( $function ) ) {
81
+ $response[$id] = call_user_func_array( $function, $params );
82
+ continue;
83
+ }
84
+
85
+
86
+ $function = $function[1];
87
+
88
+ if ( isset( $this->user_cache[$id] ) ) {
89
+ $user = $this->user_cache[$id];
90
+ } else {
91
+ $user = get_user_by( 'id', $id );
92
+ $this->user_cache[$id] = $user;
93
+ }
94
+
95
+ if ( ! is_object( $user ) || ! is_a( $user, 'WP_User' ) ) {
96
+ $response[$id] = new WP_Error( 'invalid-user-id', "Unable to find the requested user with an ID of $id." );
97
+ continue;
98
+ }
99
+
100
+ if ( ! is_callable( array( $user, $function ) ) ) {
101
+ $response[$id] = new WP_Error( "missing-function-user-$function", "Due to an unknown issue, the \$user->$function function is not available." );
102
+ continue;
103
+ }
104
+
105
+ if ( ! is_array( $params[0] ) ) {
106
+ $response[$id] = call_user_func_array( array( $user, $function ), $params );
107
+ } else {
108
+ foreach ( $params as $index => $param ) {
109
+ $response[$id][$index] = call_user_func_array( array( $user, $function ), $param );
110
+ }
111
+ }
112
+
113
+ if ( $update_user_level ) {
114
+ $user->update_user_level_from_caps();
115
+ }
116
+ }
117
+
118
+ return $response;
119
+ }
120
+ }
verbs/set-admin-bar-item-whitelist.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Implementation of the set-admin-bar-item-whitelist verb.
5
+ *
6
+ * @author Aaron D. Campbell <aaron@ithemes.com>
7
+ * @version 1.0.0
8
+ *
9
+ * Version History
10
+ * 1.0.0 - 2014-10-01 - Aaron D. Campbell
11
+ * Initial version
12
+ */
13
+
14
+
15
+ class Ithemes_Sync_Verb_Set_Admin_Bar_Item_Whitelist extends Ithemes_Sync_Verb {
16
+ public static $name = 'set-admin-bar-item-whitelist';
17
+ public static $description = 'Set the admin bar items a user can see.';
18
+
19
+ public function run( $arguments ) {
20
+ if ( empty( $arguments['id'] ) ) {
21
+ return WP_Error( 'user-id-required', 'User ID Required' );
22
+ }
23
+
24
+ if ( ! isset( $arguments['whitelist'] ) ) {
25
+ return WP_Error( 'whitelist-required', 'Whitelist is a required parameter' );
26
+ }
27
+
28
+ if ( ! is_array( $arguments['whitelist'] ) ) {
29
+ return WP_Error( 'invalid-whitelist', 'Whitelist is invalid. Please specify as an array of IDs.' );
30
+ }
31
+
32
+ $user = get_user_by( 'id', absint( $arguments['id'] ) );
33
+ if ( ! is_object( $user ) || ! is_a( $user, 'WP_User' ) ) {
34
+ return WP_Error( 'invalid-user-id', "Unable to find the requested user with an ID of {$arguments['id']}." );
35
+ }
36
+
37
+ $meta_key = 'ithemes-sync-admin-bar-item-whitelist-' . get_current_blog_id();
38
+ return update_user_meta( $user->ID, $meta_key, $arguments['whitelist'] );
39
+ }
40
+ }
verbs/update-google-site-verification-token.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the update-show-sync verb.
5
+ Written by Lew Ayotte for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2016-12-15 - Lew Ayotte
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Update_Google_Site_Verification_Token extends Ithemes_Sync_Verb {
15
+ public static $name = 'update-google-site-verification-token';
16
+ public static $description = 'Temporarily sets a meta token to verify sites w/ Google';
17
+
18
+ public function run( $arguments ) {
19
+ update_option( 'ithemes-sync-googst', array( 'code' => $arguments, 'expiry' => time() + ( 60 * 60 * 24 ) ) );
20
+
21
+ return array( 'success' => 1 );
22
+ }
23
+ }
verbs/update-show-sync.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Implementation of the update-show-sync verb.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.0.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-11-19 - Chris Jean
10
+ Initial version
11
+ */
12
+
13
+
14
+ class Ithemes_Sync_Verb_Update_Show_Sync extends Ithemes_Sync_Verb {
15
+ public static $name = 'update-show-sync';
16
+ public static $description = 'Controls whether the Sync plugin and the iThemes Sync page shows up in the WordPress Dashboard.';
17
+
18
+ private $default_arguments = array();
19
+
20
+
21
+ public function run( $arguments ) {
22
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
23
+
24
+
25
+ if ( ! isset( $arguments['show_sync'] ) ) {
26
+ return new WP_Error( 'missing-show_sync-argument', 'The show_sync argument is missing. The new show_sync value should be sent in the show_sync argument.' );
27
+ }
28
+
29
+
30
+ $options = $GLOBALS['ithemes-sync-settings']->get_options();
31
+ $options['show_sync'] = $arguments['show_sync'];
32
+
33
+ $GLOBALS['ithemes-sync-settings']->update_options( $options );
34
+
35
+
36
+ return array( 'success' => 1 );
37
+ }
38
+ }
verbs/verb.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ Parent class for all Sync verbs.
5
+ Written by Chris Jean for iThemes.com
6
+ Version 1.1.0
7
+
8
+ Version History
9
+ 1.0.0 - 2013-10-01 - Chris Jean
10
+ Initial version
11
+ 1.1.0 - 2014-01-20 - Chris Jean
12
+ Added $status_element_name and $show_in_status_by_default.
13
+ */
14
+
15
+
16
+ class Ithemes_Sync_Verb {
17
+ public static $name = 'example';
18
+ public static $description = 'This verb is not meant to be used; rather, it serves as the building block for all other verbs.';
19
+ public static $status_element_name = '';
20
+ public static $show_in_status_by_default = false;
21
+
22
+ private $default_arguments = array();
23
+
24
+
25
+ public function run( $arguments ) {
26
+ $arguments = Ithemes_Sync_Functions::merge_defaults( $arguments, $this->default_arguments );
27
+
28
+ return array();
29
+ }
30
+ }