|
1 <?php |
|
2 // $Id: update.fetch.inc,v 1.7.2.6 2009/06/09 11:08:32 goba Exp $ |
|
3 |
|
4 /** |
|
5 * @file |
|
6 * Code required only when fetching information about available updates. |
|
7 */ |
|
8 |
|
9 /** |
|
10 * Callback to manually check the update status without cron. |
|
11 */ |
|
12 function update_manual_status() { |
|
13 if (_update_refresh()) { |
|
14 drupal_set_message(t('Attempted to fetch information about all available new releases and updates.')); |
|
15 } |
|
16 else { |
|
17 drupal_set_message(t('Unable to fetch any information about available new releases and updates.'), 'error'); |
|
18 } |
|
19 drupal_goto('admin/reports/updates'); |
|
20 } |
|
21 |
|
22 /** |
|
23 * Fetch project info via XML from a central server. |
|
24 */ |
|
25 function _update_refresh() { |
|
26 static $fail = array(); |
|
27 global $base_url; |
|
28 module_load_include('inc', 'update', 'update.compare'); |
|
29 |
|
30 // Since we're fetching new available update data, we want to clear |
|
31 // our cache of both the projects we care about, and the current update |
|
32 // status of the site. We do *not* want to clear the cache of available |
|
33 // releases just yet, since that data (even if it's stale) can be useful |
|
34 // during update_get_projects(); for example, to modules that implement |
|
35 // hook_system_info_alter() such as cvs_deploy. |
|
36 _update_cache_clear('update_project_projects'); |
|
37 _update_cache_clear('update_project_data'); |
|
38 |
|
39 $available = array(); |
|
40 $data = array(); |
|
41 $site_key = md5($base_url . drupal_get_private_key()); |
|
42 $projects = update_get_projects(); |
|
43 |
|
44 // Now that we have the list of projects, we should also clear our cache of |
|
45 // available release data, since even if we fail to fetch new data, we need |
|
46 // to clear out the stale data at this point. |
|
47 _update_cache_clear('update_available_releases'); |
|
48 $max_fetch_attempts = variable_get('update_max_fetch_attempts', UPDATE_MAX_FETCH_ATTEMPTS); |
|
49 |
|
50 foreach ($projects as $key => $project) { |
|
51 $url = _update_build_fetch_url($project, $site_key); |
|
52 $fetch_url_base = _update_get_fetch_url_base($project); |
|
53 if (empty($fail[$fetch_url_base]) || count($fail[$fetch_url_base]) < $max_fetch_attempts) { |
|
54 $xml = drupal_http_request($url); |
|
55 if (isset($xml->data)) { |
|
56 $data[] = $xml->data; |
|
57 } |
|
58 else { |
|
59 // Connection likely broken; prepare to give up. |
|
60 $fail[$fetch_url_base][$key] = 1; |
|
61 } |
|
62 } |
|
63 else { |
|
64 // Didn't bother trying to fetch. |
|
65 $fail[$fetch_url_base][$key] = 1; |
|
66 } |
|
67 } |
|
68 |
|
69 if ($data) { |
|
70 $parser = new update_xml_parser; |
|
71 $available = $parser->parse($data); |
|
72 } |
|
73 if (!empty($available) && is_array($available)) { |
|
74 // Record the projects where we failed to fetch data. |
|
75 foreach ($fail as $fetch_url_base => $failures) { |
|
76 foreach ($failures as $key => $value) { |
|
77 $available[$key]['project_status'] = 'not-fetched'; |
|
78 } |
|
79 } |
|
80 $frequency = variable_get('update_check_frequency', 1); |
|
81 _update_cache_set('update_available_releases', $available, time() + (60 * 60 * 24 * $frequency)); |
|
82 watchdog('update', 'Attempted to fetch information about all available new releases and updates.', array(), WATCHDOG_NOTICE, l(t('view'), 'admin/reports/updates')); |
|
83 } |
|
84 else { |
|
85 watchdog('update', 'Unable to fetch any information about available new releases and updates.', array(), WATCHDOG_ERROR, l(t('view'), 'admin/reports/updates')); |
|
86 } |
|
87 // Whether this worked or not, we did just (try to) check for updates. |
|
88 variable_set('update_last_check', time()); |
|
89 return $available; |
|
90 } |
|
91 |
|
92 /** |
|
93 * Generates the URL to fetch information about project updates. |
|
94 * |
|
95 * This figures out the right URL to use, based on the project's .info file |
|
96 * and the global defaults. Appends optional query arguments when the site is |
|
97 * configured to report usage stats. |
|
98 * |
|
99 * @param $project |
|
100 * The array of project information from update_get_projects(). |
|
101 * @param $site_key |
|
102 * The anonymous site key hash (optional). |
|
103 * |
|
104 * @see update_refresh() |
|
105 * @see update_get_projects() |
|
106 */ |
|
107 function _update_build_fetch_url($project, $site_key = '') { |
|
108 $name = $project['name']; |
|
109 $url = _update_get_fetch_url_base($project); |
|
110 $url .= '/'. $name .'/'. DRUPAL_CORE_COMPATIBILITY; |
|
111 // Only append a site_key and the version information if we have a site_key |
|
112 // in the first place, and if this is not a disabled module or theme. We do |
|
113 // not want to record usage statistics for disabled code. |
|
114 if (!empty($site_key) && (strpos($project['project_type'], 'disabled') === FALSE)) { |
|
115 $url .= (strpos($url, '?') === TRUE) ? '&' : '?'; |
|
116 $url .= 'site_key='; |
|
117 $url .= drupal_urlencode($site_key); |
|
118 if (!empty($project['info']['version'])) { |
|
119 $url .= '&version='; |
|
120 $url .= drupal_urlencode($project['info']['version']); |
|
121 } |
|
122 } |
|
123 return $url; |
|
124 } |
|
125 |
|
126 /** |
|
127 * Return the base of the URL to fetch available update data for a project. |
|
128 * |
|
129 * @param $project |
|
130 * The array of project information from update_get_projects(). |
|
131 * @return |
|
132 * The base of the URL used for fetching available update data. This does |
|
133 * not include the path elements to specify a particular project, version, |
|
134 * site_key, etc. |
|
135 * |
|
136 * @see _update_build_fetch_url() |
|
137 */ |
|
138 function _update_get_fetch_url_base($project) { |
|
139 return isset($project['info']['project status url']) ? $project['info']['project status url'] : variable_get('update_fetch_url', UPDATE_DEFAULT_URL); |
|
140 } |
|
141 |
|
142 /** |
|
143 * Perform any notifications that should be done once cron fetches new data. |
|
144 * |
|
145 * This method checks the status of the site using the new data and depending |
|
146 * on the configuration of the site, notifies administrators via email if there |
|
147 * are new releases or missing security updates. |
|
148 * |
|
149 * @see update_requirements() |
|
150 */ |
|
151 function _update_cron_notify() { |
|
152 include_once './includes/install.inc'; |
|
153 $status = update_requirements('runtime'); |
|
154 $params = array(); |
|
155 $notify_all = (variable_get('update_notification_threshold', 'all') == 'all'); |
|
156 foreach (array('core', 'contrib') as $report_type) { |
|
157 $type = 'update_'. $report_type; |
|
158 if (isset($status[$type]['severity']) |
|
159 && ($status[$type]['severity'] == REQUIREMENT_ERROR || ($notify_all && $status[$type]['reason'] == UPDATE_NOT_CURRENT))) { |
|
160 $params[$report_type] = $status[$type]['reason']; |
|
161 } |
|
162 } |
|
163 if (!empty($params)) { |
|
164 $notify_list = variable_get('update_notify_emails', ''); |
|
165 if (!empty($notify_list)) { |
|
166 $default_language = language_default(); |
|
167 foreach ($notify_list as $target) { |
|
168 if ($target_user = user_load(array('mail' => $target))) { |
|
169 $target_language = user_preferred_language($target_user); |
|
170 } |
|
171 else { |
|
172 $target_language = $default_language; |
|
173 } |
|
174 drupal_mail('update', 'status_notify', $target, $target_language, $params); |
|
175 } |
|
176 } |
|
177 } |
|
178 } |
|
179 |
|
180 /** |
|
181 * XML Parser object to read Drupal's release history info files. |
|
182 * This uses PHP4's lame XML parsing, but it works. |
|
183 */ |
|
184 class update_xml_parser { |
|
185 var $projects = array(); |
|
186 var $current_project; |
|
187 var $current_release; |
|
188 var $current_term; |
|
189 var $current_tag; |
|
190 var $current_object; |
|
191 |
|
192 /** |
|
193 * Parse an array of XML data files. |
|
194 */ |
|
195 function parse($data) { |
|
196 foreach ($data as $datum) { |
|
197 $parser = xml_parser_create(); |
|
198 xml_set_object($parser, $this); |
|
199 xml_set_element_handler($parser, 'start', 'end'); |
|
200 xml_set_character_data_handler($parser, "data"); |
|
201 xml_parse($parser, $datum); |
|
202 xml_parser_free($parser); |
|
203 } |
|
204 return $this->projects; |
|
205 } |
|
206 |
|
207 function start($parser, $name, $attr) { |
|
208 $this->current_tag = $name; |
|
209 switch ($name) { |
|
210 case 'PROJECT': |
|
211 unset($this->current_object); |
|
212 $this->current_project = array(); |
|
213 $this->current_object = &$this->current_project; |
|
214 break; |
|
215 case 'RELEASE': |
|
216 unset($this->current_object); |
|
217 $this->current_release = array(); |
|
218 $this->current_object = &$this->current_release; |
|
219 break; |
|
220 case 'TERM': |
|
221 unset($this->current_object); |
|
222 $this->current_term = array(); |
|
223 $this->current_object = &$this->current_term; |
|
224 break; |
|
225 } |
|
226 } |
|
227 |
|
228 function end($parser, $name) { |
|
229 switch ($name) { |
|
230 case 'PROJECT': |
|
231 unset($this->current_object); |
|
232 $this->projects[$this->current_project['short_name']] = $this->current_project; |
|
233 $this->current_project = array(); |
|
234 break; |
|
235 case 'RELEASE': |
|
236 unset($this->current_object); |
|
237 $this->current_project['releases'][$this->current_release['version']] = $this->current_release; |
|
238 break; |
|
239 case 'RELEASES': |
|
240 $this->current_object = &$this->current_project; |
|
241 break; |
|
242 case 'TERM': |
|
243 unset($this->current_object); |
|
244 $term_name = $this->current_term['name']; |
|
245 if (!isset($this->current_release['terms'])) { |
|
246 $this->current_release['terms'] = array(); |
|
247 } |
|
248 if (!isset($this->current_release['terms'][$term_name])) { |
|
249 $this->current_release['terms'][$term_name] = array(); |
|
250 } |
|
251 $this->current_release['terms'][$term_name][] = $this->current_term['value']; |
|
252 break; |
|
253 case 'TERMS': |
|
254 $this->current_object = &$this->current_release; |
|
255 break; |
|
256 default: |
|
257 $this->current_object[strtolower($this->current_tag)] = trim($this->current_object[strtolower($this->current_tag)]); |
|
258 $this->current_tag = ''; |
|
259 } |
|
260 } |
|
261 |
|
262 function data($parser, $data) { |
|
263 if ($this->current_tag && !in_array($this->current_tag, array('PROJECT', 'RELEASE', 'RELEASES', 'TERM', 'TERMS'))) { |
|
264 $tag = strtolower($this->current_tag); |
|
265 if (isset($this->current_object[$tag])) { |
|
266 $this->current_object[$tag] .= $data; |
|
267 } |
|
268 else { |
|
269 $this->current_object[$tag] = $data; |
|
270 } |
|
271 } |
|
272 } |
|
273 } |