1
+ <?php
2
+ /**
3
+ * Theme Update Checker Library 1.0
4
+ * http://w-shadow.com/
5
+ *
6
+ * Copyright 2011 Janis Elsts
7
+ * Licensed under the GNU GPL license.
8
+ * http://www.gnu.org/licenses/gpl.html
9
+ */
10
+
11
+ if ( !class_exists ('ThemeUpdateChecker ' ) ):
12
+
13
+ /**
14
+ * A custom theme update checker.
15
+ *
16
+ * @author Janis Elsts
17
+ * @copyright 2011
18
+ * @version 1.0
19
+ * @access public
20
+ */
21
+ class ThemeUpdateChecker {
22
+ public $ theme = 'oitflex ' ; //The theme associated with this update checker instance.
23
+ public $ metadataUrl = 'http://resources.wordpress.ncsu.edu/oitflex/info.json ' ; //The URL of the theme's metadata file.
24
+ public $ enableAutomaticChecking = true ; //Enable/disable automatic update checks.
25
+
26
+
27
+ protected $ optionName = '' ; //Where to store update info.
28
+ protected $ automaticCheckDone = false ;
29
+ protected static $ filterPrefix = 'tuc_request_update_ ' ;
30
+
31
+ /**
32
+ * Class constructor.
33
+ *
34
+ * @param string $theme Theme slug, e.g. 'twentyten'.
35
+ * @param string $metadataUrl The URL of the theme metadata file.
36
+ * @param boolean $enableAutomaticChecking Enable/disable automatic udpate checking. If set to FALSE, you'll need to expclicitly call checkForUpdates() to, err, check for updates.
37
+ * @return void
38
+ */
39
+ public function __construct ($ theme , $ metadataUrl , $ enableAutomaticChecking = true ){
40
+ $ this ->metadataUrl = $ metadataUrl ;
41
+ $ this ->enableAutomaticChecking = $ enableAutomaticChecking ;
42
+ $ this ->theme = $ theme ;
43
+ $ this ->optionName = 'external_theme_updates- ' .$ this ->theme ;
44
+
45
+ $ this ->installHooks ();
46
+ }
47
+
48
+ /**
49
+ * Install the hooks required to run periodic update checks and inject update info
50
+ * into WP data structures.
51
+ *
52
+ * @return void
53
+ */
54
+ public function installHooks (){
55
+ //Check for updates when WordPress does. We can detect when that happens by tracking
56
+ //updates to the "update_themes" transient, which only happen in wp_update_themes().
57
+ if ( $ this ->enableAutomaticChecking ){
58
+ add_filter ('pre_set_site_transient_update_themes ' , array ($ this , 'onTransientUpdate ' ));
59
+ }
60
+
61
+ //Insert our update info into the update list maintained by WP.
62
+ add_filter ('site_transient_update_themes ' , array ($ this ,'injectUpdate ' ));
63
+
64
+ //Delete our update info when WP deletes its own.
65
+ //This usually happens when a theme is installed, removed or upgraded.
66
+ add_action ('delete_site_transient_update_themes ' , array ($ this , 'deleteStoredData ' ));
67
+ }
68
+
69
+ /**
70
+ * Retrieve update info from the configured metadata URL.
71
+ *
72
+ * Returns either an instance of ThemeUpdate, or NULL if there is
73
+ * no newer version available or if there's an error.
74
+ *
75
+ * @uses wp_remote_get()
76
+ *
77
+ * @param array $queryArgs Additional query arguments to append to the request. Optional.
78
+ * @return ThemeUpdate
79
+ */
80
+ public function requestUpdate ($ queryArgs = array ()){
81
+ //Query args to append to the URL. Themes can add their own by using a filter callback (see addQueryArgFilter()).
82
+ $ queryArgs ['installed_version ' ] = $ this ->getInstalledVersion ();
83
+ $ queryArgs = apply_filters (self ::$ filterPrefix .'query_args- ' .$ this ->theme , $ queryArgs );
84
+
85
+ //Various options for the wp_remote_get() call. Themes can filter these, too.
86
+ $ options = array (
87
+ 'timeout ' => 10 , //seconds
88
+ 'headers ' => array (
89
+ 'Accept ' => 'application/json '
90
+ ),
91
+ );
92
+ $ options = apply_filters (self ::$ filterPrefix .'options- ' .$ this ->theme , array ());
93
+
94
+ $ url = $ this ->metadataUrl ;
95
+ if ( !empty ($ queryArgs ) ){
96
+ $ url = add_query_arg ($ queryArgs , $ url );
97
+ }
98
+
99
+ //Send the request.
100
+ $ result = wp_remote_get ($ url , $ options );
101
+
102
+ //Try to parse the response
103
+ $ themeUpdate = null ;
104
+ $ code = wp_remote_retrieve_response_code ($ result );
105
+ $ body = wp_remote_retrieve_body ($ result );
106
+ if ( ($ code == 200 ) && !empty ($ body ) ){
107
+ $ themeUpdate = ThemeUpdate::fromJson ($ body );
108
+ //The update should be newer than the currently installed version.
109
+ if ( ($ themeUpdate != null ) && version_compare ($ themeUpdate ->version , $ this ->getInstalledVersion (), '<= ' ) ){
110
+ $ themeUpdate = null ;
111
+ }
112
+ }
113
+
114
+ $ themeUpdate = apply_filters (self ::$ filterPrefix .'result- ' .$ this ->theme , $ themeUpdate , $ result );
115
+ return $ themeUpdate ;
116
+ }
117
+
118
+ /**
119
+ * Get the currently installed version of our theme.
120
+ *
121
+ * @return string Version number.
122
+ */
123
+ public function getInstalledVersion (){
124
+ foreach (get_themes () as $ theme ){
125
+ if ( $ theme ['Stylesheet ' ] === $ this ->theme ){
126
+ return $ theme ['Version ' ];
127
+ }
128
+ }
129
+ return '' ;
130
+ }
131
+
132
+ /**
133
+ * Check for theme updates.
134
+ *
135
+ * @return void
136
+ */
137
+ public function checkForUpdates (){
138
+ $ state = get_option ($ this ->optionName );
139
+ if ( empty ($ state ) ){
140
+ $ state = new StdClass ;
141
+ $ state ->lastCheck = 0 ;
142
+ $ state ->checkedVersion = '' ;
143
+ $ state ->update = null ;
144
+ }
145
+
146
+ $ state ->lastCheck = time ();
147
+ $ state ->checkedVersion = $ this ->getInstalledVersion ();
148
+ update_option ($ this ->optionName , $ state ); //Save before checking in case something goes wrong
149
+
150
+ $ state ->update = $ this ->requestUpdate ();
151
+ update_option ($ this ->optionName , $ state );
152
+ }
153
+
154
+ /**
155
+ * Run the automatic update check, but no more than once per pageload.
156
+ * This is a callback for WP hooks. Do not call it directly.
157
+ *
158
+ * @param mixed $value
159
+ * @return mixed
160
+ */
161
+ public function onTransientUpdate ($ value ){
162
+ if ( !$ this ->automaticCheckDone ){
163
+ $ this ->checkForUpdates ();
164
+ $ this ->automaticCheckDone = true ;
165
+ }
166
+ return $ value ;
167
+ }
168
+
169
+ /**
170
+ * Insert the latest update (if any) into the update list maintained by WP.
171
+ *
172
+ * @param array $updates Update list.
173
+ * @return array Modified update list.
174
+ */
175
+ public function injectUpdate ($ updates ){
176
+ $ state = get_option ($ this ->optionName );
177
+
178
+ //Is there an update to insert?
179
+ if ( !empty ($ state ) && isset ($ state ->update ) && !empty ($ state ->update ) ){
180
+ $ updates ->response [$ this ->theme ] = $ state ->update ->toWpFormat ();
181
+ }
182
+
183
+ return $ updates ;
184
+ }
185
+
186
+ /**
187
+ * Delete any stored book-keeping data.
188
+ *
189
+ * @return void
190
+ */
191
+ public function deleteStoredData (){
192
+ delete_option ($ this ->optionName );
193
+ }
194
+
195
+ /**
196
+ * Register a callback for filtering query arguments.
197
+ *
198
+ * The callback function should take one argument - an associative array of query arguments.
199
+ * It should return a modified array of query arguments.
200
+ *
201
+ * @param callback $callback
202
+ * @return void
203
+ */
204
+ public function addQueryArgFilter ($ callback ){
205
+ add_filter (self ::$ filterPrefix .'query_args- ' .$ this ->themeName , $ callback );
206
+ }
207
+
208
+ /**
209
+ * Register a callback for filtering arguments passed to wp_remote_get().
210
+ *
211
+ * The callback function should take one argument - an associative array of arguments -
212
+ * and return a modified array or arguments. See the WP documentation on wp_remote_get()
213
+ * for details on what arguments are available and how they work.
214
+ *
215
+ * @param callback $callback
216
+ * @return void
217
+ */
218
+ public function addHttpRequestArgFilter ($ callback ){
219
+ add_filter (self ::$ filterPrefix .'options- ' .$ this ->themeName , $ callback );
220
+ }
221
+
222
+ /**
223
+ * Register a callback for filtering the theme info retrieved from the external API.
224
+ *
225
+ * The callback function should take two arguments. If a theme update was retrieved
226
+ * successfully, the first argument passed will be an instance of ThemeUpdate. Otherwise,
227
+ * it will be NULL. The second argument will be the corresponding return value of
228
+ * wp_remote_get (see WP docs for details).
229
+ *
230
+ * The callback function should return a new or modified instance of ThemeUpdate or NULL.
231
+ *
232
+ * @param callback $callback
233
+ * @return void
234
+ */
235
+ public function addResultFilter ($ callback ){
236
+ add_filter (self ::$ filterPrefix .'result- ' .$ this ->themeName , $ callback , 10 , 2 );
237
+ }
238
+ }
239
+
240
+ endif ;
241
+
242
+ if ( !class_exists ('ThemeUpdate ' ) ):
243
+
244
+ /**
245
+ * A simple container class for holding information about an available update.
246
+ *
247
+ * @author Janis Elsts
248
+ * @copyright 2011
249
+ * @version 1.0
250
+ * @access public
251
+ */
252
+ class ThemeUpdate {
253
+ public $ version ; //Version number.
254
+ public $ details_url ; //The URL where the user can learn more about this version.
255
+ public $ download_url ; //The download URL for this version of the theme. Optional.
256
+
257
+ /**
258
+ * Create a new instance of ThemeUpdate from its JSON-encoded representation.
259
+ *
260
+ * @param string $json Valid JSON string representing a theme information object.
261
+ * @return ThemeUpdate New instance of ThemeUpdate, or NULL on error.
262
+ */
263
+ public static function fromJson ($ json ){
264
+ $ apiResponse = json_decode ($ json );
265
+ if ( empty ($ apiResponse ) || !is_object ($ apiResponse ) ){
266
+ return null ;
267
+ }
268
+
269
+ //Very, very basic validation.
270
+ $ valid = isset ($ apiResponse ->version ) && !empty ($ apiResponse ->version ) && isset ($ apiResponse ->details_url ) && !empty ($ apiResponse ->details_url );
271
+ if ( !$ valid ){
272
+ return null ;
273
+ }
274
+
275
+ $ update = new self ();
276
+ foreach (get_object_vars ($ apiResponse ) as $ key => $ value ){
277
+ $ update ->$ key = $ value ;
278
+ }
279
+
280
+ return $ update ;
281
+ }
282
+
283
+ /**
284
+ * Transform the update into the format expected by the WordPress core.
285
+ *
286
+ * @return array
287
+ */
288
+ public function toWpFormat (){
289
+ $ update = array (
290
+ 'new_version ' => $ this ->version ,
291
+ 'url ' => $ this ->details_url ,
292
+ );
293
+
294
+ if ( !empty ($ this ->download_url ) ){
295
+ $ update ['package ' ] = $ this ->download_url ;
296
+ }
297
+
298
+ return $ update ;
299
+ }
300
+ }
301
+
302
+ endif ;
303
+
304
+ ?>
0 commit comments