-
Notifications
You must be signed in to change notification settings - Fork 0
/
epm_project_management.module
249 lines (214 loc) · 8.04 KB
/
epm_project_management.module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
<?php
include_once('epm_project_management.features.inc');
function epm_project_management_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
// Only do work on epm_project nodes.
if ($node->type != 'epm_project') {
return;
}
// @todolow: on validate, make sure the shortname is on d.o.
switch ($op) {
// On insert and update create a record in epm_project_management_data and scrape data for it.
case 'update':
case 'insert':
epm_retrieve_data($node->nid);
break;
// On load, add in some data.
case 'load':
$sql = "SELECT last_update, open_bug_count, closed_bug_count, last_stable_release, active_maintainer_count, major_version_usage FROM {epm_project_management_data} WHERE nid = %d";
$data = db_fetch_array(db_query_range($sql, array($node->nid), 0, 1));
if (empty($data)) {
epm_retrieve_data($node->nid);
$data = db_fetch_array(db_query_range($sql, array($node->nid), 0, 1));
}
$labels = _epm_project_management_metadata();
foreach ($data as $key => $value) {
if ($key == 'last_update' || $key == 'last_stable_release') {
$value = format_date($value);
}
$node->epm_project_management[] = array($labels[$key], $value);
}
break;
// On view, make that data pretty.
case 'view':
$header = array('Metric', 'Value');
$node->content['epm_project_management'] = array(
'#value' => theme('table', $header, $node->epm_project_management),
);
break;
}
}
/**
* Implements hook_cron();
*/
function epm_project_management_cron() {
// Scrape data for 20 nodes that have not been updated recently.
epm_project_management_update_projects(20);
}
/**
* A function to update projects. Called by cron and perhaps could be useful via drush.
* @param $count
* The number of nodes to update. Projects are updated from the most out of date to the newest.
*/
function epm_project_management_update_projects($count = 1) {
// Get rid of records for nodes that don't exist.
db_query("DELETE epm FROM {epm_project_management_data} epm LEFT JOIN {node} n ON epm.nid = n.nid WHERE n.nid IS NULL");
// Now update nodes.
$nodes = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {epm_project_management_data} epm ON n.nid = epm.nid WHERE n.type = 'epm_project' ORDER BY epm.last_update, n.nid ASC", 0, $count);
while ($nid = db_fetch_array($nodes)) {
epm_retrieve_data($nid);
}
}
/**
* A function to get data for a particular nid.
* @param $nid
* @return unknown_type
*/
function epm_retrieve_data($nid) {
$node = db_fetch_object(db_query("SELECT n.nid, cte.field_epm_project_shortname_value FROM {content_type_epm_project} cte INNER JOIN {node} n ON cte.vid = n.vid WHERE cte.nid = %d", $nid));
// Yet more data integrity checkup.
if (empty($node)) {
// Delete it.
db_query("DELETE FROM {epm_project_management_data} WHERE nid = %d", $nid);
}
else {
$data['nid'] = $node->nid;
// We need this.
require_once('QueryPath/QueryPath.php');
// Now get the version and start parsing it up.
$version = variable_get('epm_project_management_major_version', '6');
$data['major_version_usage'] = _epm_get_usage_data($node->field_epm_project_shortname_value, $version);
// Now a couple need the project page, so get it once and pass it around.
$doc = _epm_get_page('http://drupal.org/project/'. $node->field_epm_project_shortname_value);
list($data['open_bug_count'], $data['closed_bug_count']) = _epm_get_bug_count($doc);
$data['last_stable_release'] = _epm_get_last_stable_release($doc, $version);
$too_old = variable_get('epm_project_management_inactive_maintainer_seconds', 60*60*24*120);
$data['active_maintainer_count'] = _epm_get_active_maintainer_count($doc, $too_old);
$data['last_update'] = time();
// Delete the record, then insert it.
db_query("DELETE FROM {epm_project_management_data} WHERE nid = %d", $data['nid']);
drupal_write_record('epm_project_management_data', $data);
}
}
// A helper for our querypath work.
function _epm_get_page($url) {
$doc = new DomDocument();
@$doc->loadHTMLFile($url);
return $doc;
}
/**
* Gets usage data about a project
* @param $short_name
* A string, shortname for a project. Like 'views'.
* @param $version
* An integer for the major version of Drupal core you care about, like "6"
* @return unknown_type
*/
function _epm_get_usage_data($short_name, $version) {
// Grab the doc and parse it.
$doc = _epm_get_page('http://drupal.org/project/usage/'. $short_name);
$qp = htmlqp($doc, '#project-usage-project-api');
if ($qp) {
// First find the column we care about.
// Basically, we find the one we want, and then count how many
// th's are before it. This gives us a suitable offset.
$column_number = $qp->branch('th:contains(' . $version . '.x)')->prevAll('th')->size();
$data = array();
// Next find the row with usage.
// Now we loop through the actual table.
foreach ($qp->find('>tbody>tr') as $row) {
// Get the td's and then get the value of the first column.
$date = $row->children('td')->eq(0)->text();
// Shift back to all of the td's, and then get the $col_num'th column.
$download_total = str_replace(',', '', $row->end()->eq($column_number)->text());
// We only care about the first record.
break;
}
}
return $download_total;
}
/**
* Get the bug counts.
* @param $doc
* The project page.
* @return array with two integer elements: open bugs, closed bugs. Defaults to 0,0.
*/
function _epm_get_bug_count($doc) {
$open_bugs = $total_bugs = $closed_bugs = 0;
// Grab the issue-cockpit piece we care about.
$qp = htmlqp($doc, '.issue-cockpit-bug');
if ($qp) {
foreach ($qp->find('a') as $row) {
$bits = explode(' ', $row->text());
if ($bits[1] == 'open') {
$open_bugs = ($bits[0]);
}
elseif ($bits[1] == 'total') {
$total_bugs = ($bits[0]);
}
}
}
// Math is fun. Let's not go shopping.
$closed_bugs = $total_bugs - $open_bugs;
return array($open_bugs, $closed_bugs);
}
/**
* Finds the date of the most recent stable release.
* @param $doc
* string An HTML project page.
* @param $version
* integer The version you wish to use like 6.
* @return unknown_type
* integer A unix timestamp for the date of the release.
*/
function _epm_get_last_stable_release($doc, $version) {
$qp = htmlqp($doc, '.view-project-release-download-table');
if ($qp) {
// We've got table rows.
foreach ($qp->find('tr') as $row) {
// And this version is a specific one of those.
$this_version = $row->branch()->find('td.views-field-version')->text();
// And if this version is the version important to the site.
if (!empty($this_version) && strpos($this_version, $version)) {
// Get that release date and get out of here!
$release_date = $row->find('td.views-field-file-timestamp')->text();
return strtotime($release_date);
}
}
}
}
/**
* Count the active maintainers.
* @param $doc
* string An HTML project page.
* @return unknown_type
* integer A count of "active maintainers".
*/
function _epm_get_active_maintainer_count($doc, $too_old) {
$qp = htmlqp($doc, '#block-cvs-cvs_project_maintainers');
$count = 0;
if ($qp) {
foreach ($qp->find('li') as $user) {
$times = $user->branch()->find('div.cvs-commit-times')->text();
$bits = explode(', ', $times);
$last = strtotime(drupal_substr($bits[0], strpos($bits[0], ':') +2));
if (abs(time() - $last) < $too_old) {
$count++;
}
}
}
return $count;
}
/**
* Convenience function to get currently supported names, labels.
* @return array of labels keyed on machine names.
*/
function _epm_project_management_metadata() {
return array(
'last_update' => t('Last update'),
'open_bug_count' => t('Open bugs'),
'closed_bug_count' => t('Closed bugs'),
'last_stable_release' => t('Last stable release'),
'active_maintainer_count' => t('Active maintainers'),
'major_version_usage' => t('Major version usage'),
);
}