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