| 1 |
<?php
|
| 2 |
// $Id: metrics.module,v 1.4 2007/08/20 18:52:19 drewish Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Implementation of hook_help().
|
| 6 |
*/
|
| 7 |
function metrics_help($section) {
|
| 8 |
switch ($section) {
|
| 9 |
case 'admin/help#metrics':
|
| 10 |
return t("<p>Overview of the parts of Metrics.</p>
|
| 11 |
<dl>
|
| 12 |
<dt>Metric</dt>
|
| 13 |
<dt>A metric is a function that takes a node and computes a numeric score and an explanatory string.</dt>
|
| 14 |
<dt>Ranking</dt>
|
| 15 |
<dt>A ranking has a name, a numeric threshold, and a description. The ranking's purpose is to help a user interpret a property's score. Is a score of 30 is good or bad?</dt>
|
| 16 |
<dt>Property</dt>
|
| 17 |
<dt>A property has a name and a description to explain what it measures. The property is made up of metrics and rankings. Each metric within the property can have options and a multiplier. The multiplier is used to adjust the importance of each metric in the overall score.</dt>
|
| 18 |
<dt>Property result</dt>
|
| 19 |
<dt>When a property is computed for a node, each metrics is evaluated, the numeric value multiplied by the multiplier then added to the total, and the explanatory strings concatenated. This is stored in the database and displayed in a block on the node's page.</dt>
|
| 20 |
</dl>
|
| 21 |
<p>The property results are computed during a cron run by first looking for missing results and then updating the most out of date. The administrator can determine how frequently the properties are updated and how many are updated in a single cron run.</p>
|
| 22 |
");
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
| 26 |
/**
|
| 27 |
* Implementation of hook_menu().
|
| 28 |
*/
|
| 29 |
function metrics_menu($may_cache) {
|
| 30 |
$items = array();
|
| 31 |
|
| 32 |
if ($may_cache) {
|
| 33 |
$items[] = array(
|
| 34 |
'path' => 'admin/content/metrics',
|
| 35 |
'title' => t('Metrics'),
|
| 36 |
'callback' => 'metrics_admin_overview',
|
| 37 |
'description' => t('Configure the settings used to rate node quality.'),
|
| 38 |
);
|
| 39 |
$items[] = array(
|
| 40 |
'path' => 'admin/content/metrics/list',
|
| 41 |
'title' => t('List'),
|
| 42 |
'type' => MENU_DEFAULT_LOCAL_TASK,
|
| 43 |
'weight' => '-10',
|
| 44 |
);
|
| 45 |
$items[] = array(
|
| 46 |
'path' => 'admin/content/metrics/add',
|
| 47 |
'callback' => 'drupal_get_form',
|
| 48 |
'callback arguments' => array('metrics_property_form'),
|
| 49 |
'title' => t('Add'),
|
| 50 |
'type' => MENU_LOCAL_TASK,
|
| 51 |
);
|
| 52 |
$items[] = array(
|
| 53 |
'path' => 'admin/settings/metrics',
|
| 54 |
'title' => t('Metrics'),
|
| 55 |
'callback' => 'drupal_get_form',
|
| 56 |
'callback arguments' => array('metrics_settings_form'),
|
| 57 |
'description' => t('Configure the settings used to rate node quality.'),
|
| 58 |
);
|
| 59 |
}
|
| 60 |
else {
|
| 61 |
if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'metrics' && is_numeric(arg(3))) {
|
| 62 |
$property_id = (int) arg(3);
|
| 63 |
if ($property = metrics_get_property($property_id)) {
|
| 64 |
|
| 65 |
$items[] = array(
|
| 66 |
'path' => 'admin/content/metrics/'. $property_id,
|
| 67 |
'title' => t('Property'),
|
| 68 |
'callback' => 'metrics_property_overview',
|
| 69 |
'callback arguments' => array($property_id),
|
| 70 |
'type' => MENU_CALLBACK,
|
| 71 |
);
|
| 72 |
$items[] = array(
|
| 73 |
'path' => 'admin/content/metrics/'. $property_id .'/view',
|
| 74 |
'title' => t('View'),
|
| 75 |
'type' => MENU_DEFAULT_LOCAL_TASK,
|
| 76 |
'weight' => -10,
|
| 77 |
);
|
| 78 |
$items[] = array(
|
| 79 |
'path' => 'admin/content/metrics/'. $property_id .'/edit',
|
| 80 |
'title' => t('Edit'),
|
| 81 |
'callback' => 'metrics_property_edit',
|
| 82 |
'callback arguments' => array($property_id),
|
| 83 |
'type' => MENU_LOCAL_TASK,
|
| 84 |
);
|
| 85 |
$items[] = array(
|
| 86 |
'path' => 'admin/content/metrics/'. $property_id .'/metrics',
|
| 87 |
'title' => t('Metrics'),
|
| 88 |
'callback' => 'drupal_get_form',
|
| 89 |
'callback arguments' => array('metrics_metrics_form', $property),
|
| 90 |
'type' => MENU_LOCAL_TASK,
|
| 91 |
);
|
| 92 |
$items[] = array(
|
| 93 |
'path' => 'admin/content/metrics/'. $property_id .'/rankings',
|
| 94 |
'title' => t('Rankings'),
|
| 95 |
'callback' => 'drupal_get_form',
|
| 96 |
'callback arguments' => array('metrics_rankings_form', $property),
|
| 97 |
'type' => MENU_LOCAL_TASK,
|
| 98 |
);
|
| 99 |
$items[] = array(
|
| 100 |
'path' => 'admin/content/metrics/'. $property_id .'/delete',
|
| 101 |
'title' => t('Delete'),
|
| 102 |
'callback' => 'drupal_get_form',
|
| 103 |
'callback arguments' => array('metrics_metric_delete_form', $property_id),
|
| 104 |
'type' => MENU_CALLBACK,
|
| 105 |
);
|
| 106 |
}
|
| 107 |
}
|
| 108 |
}
|
| 109 |
|
| 110 |
return $items;
|
| 111 |
}
|
| 112 |
|
| 113 |
|
| 114 |
|
| 115 |
/**
|
| 116 |
* Implementation of hook_block().
|
| 117 |
*/
|
| 118 |
function metrics_block($op = 'list', $delta = 0, $edit = array()) {
|
| 119 |
switch ($op) {
|
| 120 |
case 'list':
|
| 121 |
$blocks[0] = array(
|
| 122 |
'info' => t('Project Metrics'),
|
| 123 |
'status' => TRUE,
|
| 124 |
'weight' => 0,
|
| 125 |
'visibility' => 1,
|
| 126 |
'pages' => 'node/*',
|
| 127 |
);
|
| 128 |
return $blocks;
|
| 129 |
|
| 130 |
case 'configure':
|
| 131 |
return $form;
|
| 132 |
|
| 133 |
case 'view':
|
| 134 |
default:
|
| 135 |
switch ($delta) {
|
| 136 |
case 0:
|
| 137 |
if ($output = metrics_block_display((int) arg(1))) {
|
| 138 |
return array(
|
| 139 |
'subject' => t('Project metrics...'),
|
| 140 |
'content' => $output,
|
| 141 |
);
|
| 142 |
}
|
| 143 |
break;
|
| 144 |
|
| 145 |
default:
|
| 146 |
return array();
|
| 147 |
}
|
| 148 |
}
|
| 149 |
}
|
| 150 |
|
| 151 |
|
| 152 |
function metrics_block_display($nid) {
|
| 153 |
if ($node = node_load($nid)) {
|
| 154 |
$items = array();
|
| 155 |
foreach ((array) $node->metrics as $property_id => $result) {
|
| 156 |
$items[] = t('@metric<br /><strong>Score: @score</strong><br />@description', array('@metric' => $result['name'], '@score' => $result['value'], '@description' => $result['description']));
|
| 157 |
}
|
| 158 |
if ($items) {
|
| 159 |
return theme('item_list', $items);
|
| 160 |
}
|
| 161 |
}
|
| 162 |
}
|
| 163 |
|
| 164 |
/**
|
| 165 |
* Implementation of hook_cron().
|
| 166 |
*/
|
| 167 |
function metrics_cron() {
|
| 168 |
$limit = variable_get('metrics_cron_limit', 50);
|
| 169 |
$timestamp = time() - variable_get('metrics_recompute_after', 604800);
|
| 170 |
|
| 171 |
// Locate nodes without {metrics_property_result} records.
|
| 172 |
$result = db_query_range('SELECT mpnt.property_id, n.nid FROM {metrics_property_node_types} mpnt INNER JOIN {node} n ON mpnt.type = n.type AND n.status = 1 WHERE NOT EXISTS
|
| 173 |
( SELECT * FROM {metrics_property_result} mpr WHERE mpr.property_id = mpnt.property_id AND mpr.nid = n.nid )', 0, $limit);
|
| 174 |
while ($row = db_fetch_object($result)) {
|
| 175 |
if ($node = node_load($row->nid)) {
|
| 176 |
metrics_compute_property($row->property_id, $node);
|
| 177 |
}
|
| 178 |
}
|
| 179 |
|
| 180 |
// Count the preceding rows against the limit.
|
| 181 |
$limit -= db_num_rows($result);
|
| 182 |
if ($limit < 1) {
|
| 183 |
return;
|
| 184 |
}
|
| 185 |
|
| 186 |
// Update stale records.
|
| 187 |
$result = db_query_range('SELECT mpr.nid, mpr.property_id FROM {metrics_property_result} mpr INNER JOIN {node} n ON mpr.nid = n.nid WHERE mpr.timestamp < %d AND n.status = 1', $timestamp, 0, $limit);
|
| 188 |
while ($row = db_fetch_object($result)) {
|
| 189 |
if ($node = node_load($row->nid)) {
|
| 190 |
metrics_compute_property($row->property_id, $node);
|
| 191 |
}
|
| 192 |
}
|
| 193 |
}
|
| 194 |
|
| 195 |
|
| 196 |
/**
|
| 197 |
* Implementation of hook_nodeapi().
|
| 198 |
*/
|
| 199 |
function metrics_nodeapi(&$node, $op, $teaser) {
|
| 200 |
switch ($op) {
|
| 201 |
case 'load':
|
| 202 |
if ($result = db_query('SELECT mp.property_id, mp.name, mpr.value, mpr.description FROM {metrics_property} mp INNER JOIN {metrics_property_result} mpr ON mp.property_id = mpr.property_id WHERE mpr.nid = %d', $node->nid)) {
|
| 203 |
$output = array();
|
| 204 |
while ($o = db_fetch_array($result)) {
|
| 205 |
$output['metrics'][$o['property_id']] = $o;
|
| 206 |
}
|
| 207 |
return $output;
|
| 208 |
}
|
| 209 |
break;
|
| 210 |
|
| 211 |
case 'insert':
|
| 212 |
case 'update':
|
| 213 |
// Mark all of our results as stale so they'll be re-computed.
|
| 214 |
db_query("UPDATE {metrics_property_result} SET timestamp = 0 WHERE nid = %d", $node->nid);
|
| 215 |
break;
|
| 216 |
|
| 217 |
case 'delete':
|
| 218 |
db_query('DELETE FROM {metrics_property_result} WHERE nid = %d', $node->nid);
|
| 219 |
break;
|
| 220 |
}
|
| 221 |
}
|
| 222 |
|
| 223 |
|
| 224 |
|
| 225 |
|
| 226 |
|
| 227 |
/**
|
| 228 |
* Return an array of all metric objects.
|
| 229 |
*
|
| 230 |
* TODO: this should take node type as a parameter.
|
| 231 |
*/
|
| 232 |
function metrics_get_properties() {
|
| 233 |
$properties = array();
|
| 234 |
|
| 235 |
// Ensure that the .inc files are loaded.
|
| 236 |
_metrics_get_metrics_functions();
|
| 237 |
|
| 238 |
$result = db_query('SELECT * FROM {metrics_property} mp ORDER BY mp.name');
|
| 239 |
while ($property = db_fetch_array($result)) {
|
| 240 |
$property['node_types'] = array();
|
| 241 |
$sub_result = db_query('SELECT type FROM {metrics_property_node_types} mpnt WHERE property_id = %d', $property['property_id']);
|
| 242 |
while ($type = db_fetch_object($sub_result)) {
|
| 243 |
$property['node_types'][$type->type] = $type->type;
|
| 244 |
}
|
| 245 |
|
| 246 |
$property['metrics'] = array();
|
| 247 |
$sub_result = db_query('SELECT metric_id, function_name, options, displayed, multiplier FROM {metrics_metric} mm WHERE property_id = %d ORDER BY function_name', $property['property_id']);
|
| 248 |
while ($metric = db_fetch_array($sub_result)) {
|
| 249 |
if (isset($metric['options']) && $options = unserialize($metric['options'])) {
|
| 250 |
$metric['options'] = $options;
|
| 251 |
}
|
| 252 |
else {
|
| 253 |
$metric['options'] = array();
|
| 254 |
}
|
| 255 |
$property['metrics'][$metric['metric_id']] = $metric;
|
| 256 |
}
|
| 257 |
|
| 258 |
$property['rankings'] = array();
|
| 259 |
$sub_result = db_query('SELECT ranking_id, name, threshold, description FROM {metrics_ranking} mr WHERE property_id = %d ORDER BY threshold DESC', $property['property_id']);
|
| 260 |
while ($ranking = db_fetch_array($sub_result)) {
|
| 261 |
$property['rankings'][$ranking['ranking_id']] = $ranking;
|
| 262 |
}
|
| 263 |
|
| 264 |
$properties[$property['property_id']] = $property;
|
| 265 |
}
|
| 266 |
|
| 267 |
return $properties;
|
| 268 |
}
|
| 269 |
|
| 270 |
/**
|
| 271 |
* Return the property with a given ID.
|
| 272 |
*
|
| 273 |
* @param $property_id
|
| 274 |
* The metric's ID
|
| 275 |
*
|
| 276 |
* @return Object
|
| 277 |
* The metric object with all of its metadata.
|
| 278 |
* Results are statically cached.
|
| 279 |
*/
|
| 280 |
function metrics_get_property($property_id) {
|
| 281 |
static $properties = array();
|
| 282 |
|
| 283 |
if (!array_key_exists($property_id, $properties)) {
|
| 284 |
$properties = metrics_get_properties();
|
| 285 |
}
|
| 286 |
|
| 287 |
return $properties[$property_id];
|
| 288 |
}
|
| 289 |
|
| 290 |
/**
|
| 291 |
* Recompute the
|
| 292 |
*
|
| 293 |
* @param $property_id
|
| 294 |
* Integer property ID.
|
| 295 |
* @param $node
|
| 296 |
* Node object.
|
| 297 |
* @return
|
| 298 |
* The numeric result of the property, or FALSE on error.
|
| 299 |
*/
|
| 300 |
function metrics_compute_property($property_id, $node) {
|
| 301 |
if ($property = metrics_get_property($property_id)) {
|
| 302 |
// Go ahead and delete first because if we can't compute it there shouldn't
|
| 303 |
// be a row in the database for it.
|
| 304 |
db_query('DELETE FROM {metrics_property_result} WHERE property_id = %d AND nid = %d', $property_id, $node->nid);
|
| 305 |
|
| 306 |
if (isset($property['node_types'][$node->type])) {
|
| 307 |
$total = 0.0;
|
| 308 |
$description = '';
|
| 309 |
foreach ($property['metrics'] as $metric_id => $metric) {
|
| 310 |
if ($result = metrics_call_metric($metric, 'compute', $node)) {
|
| 311 |
// Add up the numeric part.
|
| 312 |
$total += $result['value'] * $metric['multiplier'];
|
| 313 |
// If the metric is displayed include the string part.
|
| 314 |
$description .= $metric['displayed'] ? ($result['description'] . ' ') : '';
|
| 315 |
}
|
| 316 |
}
|
| 317 |
|
| 318 |
// Determine which ranking this value falls into. We assume that
|
| 319 |
// metrics_get_property() returns the rankings sorted by descending
|
| 320 |
// threshold value.
|
| 321 |
$last_ranking_id = NULL;
|
| 322 |
foreach ($property['rankings'] as $ranking_id => $ranking) {
|
| 323 |
if ($ranking['threshold'] < $total) {
|
| 324 |
break;
|
| 325 |
}
|
| 326 |
}
|
| 327 |
|
| 328 |
db_query("INSERT INTO {metrics_property_result} (property_id, nid, ranking_id, value, description, timestamp) VALUES (%d, %d, %d, %d, '%s', %d)", $property_id, $node->nid, $ranking_id, $total, $description, time());
|
| 329 |
# drupal_set_message(t("%property metrics was computed for %node (@score): @description", array('%property' => $property['name'], '%node' => $node->title, '@score' => $total, '@description' => $description)));
|
| 330 |
return $total;
|
| 331 |
}
|
| 332 |
}
|
| 333 |
return FALSE;
|
| 334 |
}
|
| 335 |
|
| 336 |
/**
|
| 337 |
* Return an array of the metrics computation functions.
|
| 338 |
*
|
| 339 |
* @param $refresh
|
| 340 |
* Boolean indicating if the list should be fully recomputed.
|
| 341 |
* @return
|
| 342 |
* An array of function names.
|
| 343 |
*/
|
| 344 |
function _metrics_get_metrics_functions($refresh = FALSE) {
|
| 345 |
static $_functions;
|
| 346 |
|
| 347 |
if ($refresh || !isset($_functions)) {
|
| 348 |
$_functions = array();
|
| 349 |
|
| 350 |
// Load our .inc files that provide metrics for other projects.
|
| 351 |
$path = drupal_get_path('module', 'metrics') .'/modules';
|
| 352 |
$files = drupal_system_listing('.inc$', $path, 'name', 0);
|
| 353 |
foreach ($files as $file) {
|
| 354 |
// Only load the file if a module of that same name exists.
|
| 355 |
if (module_exists($file->name)) {
|
| 356 |
require_once('./' . $file->filename);
|
| 357 |
$function = 'metrics_'. $file->name . '_metrics_functions';
|
| 358 |
$result = $function();
|
| 359 |
if (isset($result) && is_array($result)) {
|
| 360 |
$_functions = array_merge($_functions, $result);
|
| 361 |
}
|
| 362 |
}
|
| 363 |
}
|
| 364 |
|
| 365 |
// Invoke the hook and return the results.
|
| 366 |
$_functions = array_merge($_functions, module_invoke_all('metrics_functions'));
|
| 367 |
|
| 368 |
// Only store functions that exist.
|
| 369 |
$_functions = array_filter($_functions, 'function_exists');
|
| 370 |
}
|
| 371 |
|
| 372 |
return $_functions;
|
| 373 |
}
|
| 374 |
|
| 375 |
/**
|
| 376 |
* Helper function to call metrics. This is used rather than
|
| 377 |
* $metric['function_name']() because it makes it easier to locate calls in
|
| 378 |
* code.
|
| 379 |
*
|
| 380 |
* @param $metric
|
| 381 |
* Metric array.
|
| 382 |
* @param $op
|
| 383 |
* String indicating the operation to call. Possible values are:
|
| 384 |
* - 'info' returns an array with a name and description.
|
| 385 |
* - 'compute' returns an array with a numeric and string value.
|
| 386 |
* - 'options' returns an array of form elements defining the metric's
|
| 387 |
* options.
|
| 388 |
* @return
|
| 389 |
* Mixed depending on the $op or FALSE if the function doesn't exist.
|
| 390 |
*/
|
| 391 |
function metrics_call_metric($metric, $op, $node = NULL) {
|
| 392 |
if (function_exists($metric['function_name'])) {
|
| 393 |
return $metric['function_name']($op, $metric['options'], $node);
|
| 394 |
}
|
| 395 |
return FALSE;
|
| 396 |
}
|
| 397 |
|
| 398 |
/**
|
| 399 |
* Module settings form.
|
| 400 |
*/
|
| 401 |
function metrics_settings_form() {
|
| 402 |
$times = array(6048000, 5443200, 4838400, 4233600, 3628800, 3024000, 2419200, 1814400, 1209600, 604800, 518400, 432000, 345600, 259200, 172800, 86400);
|
| 403 |
$ageoptions = drupal_map_assoc($times, 'format_interval');
|
| 404 |
$form['metrics_recompute_after'] = array(
|
| 405 |
'#type' => 'select',
|
| 406 |
'#title' => t('Update after'),
|
| 407 |
'#default_value' => variable_get('metrics_recompute_after', 604800),
|
| 408 |
'#options' => $ageoptions,
|
| 409 |
'#description' => t("Once the metrics are older than this, they will be updated during a cron run."),
|
| 410 |
);
|
| 411 |
|
| 412 |
$limit = drupal_map_assoc(array(5, 10, 15, 20, 25, 50, 100, 200, 300, 500));
|
| 413 |
$form['metrics_cron_limit'] = array(
|
| 414 |
'#type' => 'select',
|
| 415 |
'#title' => t('Cron limit'),
|
| 416 |
'#default_value' => variable_get('metrics_cron_limit', 50),
|
| 417 |
'#options' => $limit,
|
| 418 |
'#description' => t("The number of nodes to recompute each cron run."),
|
| 419 |
);
|
| 420 |
|
| 421 |
return system_settings_form($form);
|
| 422 |
}
|
| 423 |
|
| 424 |
|
| 425 |
/**
|
| 426 |
* Project metrics overview settings, display a list of metrics.
|
| 427 |
*/
|
| 428 |
function metrics_admin_overview() {
|
| 429 |
$properties = metrics_get_properties();
|
| 430 |
$node_types = node_get_types('names');
|
| 431 |
|
| 432 |
$rows = array();
|
| 433 |
foreach ($properties as $property_id => $property) {
|
| 434 |
$types = array();
|
| 435 |
foreach ($property['node_types'] as $type) {
|
| 436 |
if (isset($node_types[$type])) {
|
| 437 |
$types[] = $node_types[$type];
|
| 438 |
}
|
| 439 |
}
|
| 440 |
$rows[] = array(
|
| 441 |
'name' => l(check_plain($property['name']), "admin/content/metrics/$property_id"),
|
| 442 |
'type' => implode(', ', $types),
|
| 443 |
'edit' => l(t('edit metric'), "admin/content/metrics/$property_id/edit"),
|
| 444 |
);
|
| 445 |
}
|
| 446 |
if (empty($rows)) {
|
| 447 |
$rows[] = array(array('data' => t('No Properties exist.'), 'colspan' => '3'));
|
| 448 |
}
|
| 449 |
$header = array(t('Name'), t('Type'), t('Operations'));
|
| 450 |
|
| 451 |
return theme('table', $header, $rows);
|
| 452 |
}
|
| 453 |
|
| 454 |
|
| 455 |
function metrics_property_overview($property_id) {
|
| 456 |
$property = metrics_get_property($property_id);
|
| 457 |
if (!$property) {
|
| 458 |
return drupal_not_found();
|
| 459 |
}
|
| 460 |
|
| 461 |
drupal_set_title($property['name']);
|
| 462 |
|
| 463 |
$content['name'] = array(
|
| 464 |
'#type' => 'item',
|
| 465 |
'#title' => t('Property name'),
|
| 466 |
'#value' => check_plain($property['name']),
|
| 467 |
);
|
| 468 |
$content['description'] = array(
|
| 469 |
'#type' => 'item',
|
| 470 |
'#title' => t('Description'),
|
| 471 |
'#value' => check_plain($property['description']),
|
| 472 |
);
|
| 473 |
$types = array();
|
| 474 |
$node_types = node_get_types('names');
|
| 475 |
foreach ($property['node_types'] as $type) {
|
| 476 |
if (isset($node_types[$type])) {
|
| 477 |
$types[] = $node_types[$type];
|
| 478 |
}
|
| 479 |
}
|
| 480 |
$content['node_types'] = array(
|
| 481 |
'#type' => 'item',
|
| 482 |
'#title' => t('Node types'),
|
| 483 |
'#value' => check_plain(implode(', ', $types)),
|
| 484 |
);
|
| 485 |
|
| 486 |
$rows = array();
|
| 487 |
foreach ((array) $property['metrics'] as $metric_id => $metric) {
|
| 488 |
$info = metrics_call_metric($metric, 'info');
|
| 489 |
$rows[] = array(
|
| 490 |
$info['name'],
|
| 491 |
$metric['multiplier'],
|
| 492 |
$metric['displayed'] ? t('Yes') : '',
|
| 493 |
);
|
| 494 |
}
|
| 495 |
if (!$rows) {
|
| 496 |
$rows[][] = array('data' => t('No metrics found.'), 'colspan' => 3);
|
| 497 |
}
|
| 498 |
$header = array(t('Name'), t('Multiplier'), t('Displayed'));
|
| 499 |
$content['metrics'] = array(
|
| 500 |
'#type' => 'item',
|
| 501 |
'#title' => t('Metrics'),
|
| 502 |
'#value' => theme('table', $header, $rows),
|
| 503 |
'#weight' => 5,
|
| 504 |
);
|
| 505 |
|
| 506 |
$rows = array();
|
| 507 |
foreach ((array) $property['rankings'] as $ranking_id => $ranking) {
|
| 508 |
$rows[] = array(
|
| 509 |
$ranking['name'],
|
| 510 |
$ranking['threshold'],
|
| 511 |
$ranking['description'],
|
| 512 |
);
|
| 513 |
}
|
| 514 |
if (!$rows) {
|
| 515 |
$rows[][] = array('data' => t('No rankings found.'), 'colspan' => 4);
|
| 516 |
}
|
| 517 |
$header = array(t('Name'), t('Threshold'), t('Description'));
|
| 518 |
$content['rankings'] = array(
|
| 519 |
'#type' => 'item',
|
| 520 |
'#title' => t('Rankings'),
|
| 521 |
'#value' => theme('table', $header, $rows),
|
| 522 |
'#weight' => 6,
|
| 523 |
);
|
| 524 |
|
| 525 |
return drupal_render($content);
|
| 526 |
}
|
| 527 |
|
| 528 |
function metrics_property_edit($property_id = NULL) {
|
| 529 |
if ($_POST['op'] == t('Delete') || $_POST['confirm']) {
|
| 530 |
drupal_goto('admin/content/metrics/'. $property_id .'/delete');
|
| 531 |
}
|
| 532 |
if ($property = metrics_get_property($property_id)) {
|
| 533 |
return drupal_get_form('metrics_property_form', $property);
|
| 534 |
}
|
| 535 |
return drupal_not_found();
|
| 536 |
}
|
| 537 |
|
| 538 |
/**
|
| 539 |
* Property edit form.
|
| 540 |
*/
|
| 541 |
function metrics_property_form($edit = array()) {
|
| 542 |
if (isset($edit['name'])) {
|
| 543 |
drupal_set_title(check_plain($edit['name']));
|
| 544 |
}
|
| 545 |
|
| 546 |
$form['name'] = array(
|
| 547 |
'#type' => 'textfield',
|
| 548 |
'#title' => t('Property name'),
|
| 549 |
'#default_value' => $edit['name'],
|
| 550 |
'#maxlength' => 255,
|
| 551 |
'#required' => TRUE,
|
| 552 |
);
|
| 553 |
$form['description'] = array(
|
| 554 |
'#type' => 'textarea',
|
| 555 |
'#title' => t('Description'),
|
| 556 |
'#default_value' => $edit['description'],
|
| 557 |
);
|
| 558 |
$form['node'] = array(
|
| 559 |
'#type' => 'fieldset',
|
| 560 |
'#title' => t('Node types'),
|
| 561 |
'#collapsible' => TRUE,
|
| 562 |
'#collapsed' => FALSE,
|
| 563 |
'#description' => t('The property will be measured for these node types.'),
|
| 564 |
);
|
| 565 |
$form['node']['node_types'] = array(
|
| 566 |
'#type' => 'checkboxes',
|
| 567 |
'#options' => node_get_types('names'),
|
| 568 |
'#default_value' => $edit['node_types'],
|
| 569 |
);
|
| 570 |
|
| 571 |
$form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
|
| 572 |
if ($edit['property_id']) {
|
| 573 |
$form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
|
| 574 |
$form['property_id'] = array('#type' => 'value', '#value' => $edit['property_id']);
|
| 575 |
}
|
| 576 |
return $form;
|
| 577 |
}
|
| 578 |
|
| 579 |
|
| 580 |
function metrics_property_form_submit($form_id, $form_values) {
|
| 581 |
if (!empty($form_values['property_id'])) {
|
| 582 |
db_query("UPDATE {metrics_property} SET name = '%s', description = '%s' WHERE property_id = %d", $form_values['name'], $form_values['description'], $form_values['property_id']);
|
| 583 |
|
| 584 |
// Figure out which node types have been added and removed.
|
| 585 |
$new_types = array_keys(array_filter($form_values['node_types']));
|
| 586 |
$old_types = array();
|
| 587 |
$result = db_query('SELECT type FROM {metrics_property_node_types} mpnt WHERE property_id = %d', $form_values['property_id']);
|
| 588 |
while ($o = db_fetch_object($result)) {
|
| 589 |
$old_types[] = $o->type;
|
| 590 |
}
|
| 591 |
// Removals.
|
| 592 |
foreach (array_diff($old_types, $new_types) as $type) {
|
| 593 |
// When removing node type we should remove any {metrics_property_result} records for nodes of that type.
|
| 594 |
db_query("DELETE FROM {metrics_property_result} WHERE property_id = %d AND nid IN (SELECT nid FROM {node} n WHERE n.type = '%s')", $form_values['property_id'], $type);
|
| 595 |
db_query("DELETE FROM {metrics_property_node_types} WHERE property_id = %d AND type = '%s'", $form_values['property_id'], $type);
|
| 596 |
}
|
| 597 |
// Additions.
|
| 598 |
foreach (array_diff($new_types, $old_types) as $type) {
|
| 599 |
db_query("INSERT INTO {metrics_property_node_types} (property_id, type) VALUES (%d, '%s')", $form_values['property_id'], $type);
|
| 600 |
}
|
| 601 |
}
|
| 602 |
else {
|
| 603 |
$form_values['property_id'] = db_next_id('{metrics_property}_property_id');
|
| 604 |
db_query("INSERT INTO {metrics_property} (property_id, name, description) VALUES (%d, '%s', '%s')", $form_values['property_id'], $form_values['name'], $form_values['description']);
|
| 605 |
foreach (array_filter($form_values['node_types']) as $type => $selected) {
|
| 606 |
db_query("INSERT INTO {metrics_property_node_types} (property_id, type) VALUES (%d, '%s')", $form_values['property_id'], $type);
|
| 607 |
}
|
| 608 |
}
|
| 609 |
return 'admin/content/metrics/'. $form_values['property_id'];
|
| 610 |
}
|
| 611 |
|
| 612 |
function metrics_metric_delete_form($property_id) {
|
| 613 |
$property = metrics_get_property($property_id);
|
| 614 |
|
| 615 |
$form['property_id'] = array('#type' => 'value', '#value' => $property_id);
|
| 616 |
$form['name'] = array('#type' => 'value', '#value' => $property['name']);
|
| 617 |
return confirm_form(
|
| 618 |
$form,
|
| 619 |
t('Are you sure you want to delete the property %title?', array('%title' => $property['name'])),
|
| 620 |
'admin/content/metrics',
|
| 621 |
t('Deleting a property will delete all the metrics, results and rankings associated with it. This action cannot be undone.'),
|
| 622 |
t('Delete'),
|
| 623 |
t('Cancel')
|
| 624 |
);
|
| 625 |
}
|
| 626 |
|
| 627 |
function metrics_metric_delete_form_submit($form_id, $form_values) {
|
| 628 |
metrics_metric_delete($form_values['property_id']);
|
| 629 |
|
| 630 |
drupal_set_message(t('Deleted metric %name.', array('%name' => $form_values['name'])));
|
| 631 |
watchdog('metrics', t('Deleted metric %name.', array('%name' => $form_values['name'])), WATCHDOG_NOTICE);
|
| 632 |
return 'admin/content/metrics';
|
| 633 |
}
|
| 634 |
|
| 635 |
/**
|
| 636 |
* Delete a metric.
|
| 637 |
*
|
| 638 |
* @param $property_id
|
| 639 |
* The metric's ID.
|
| 640 |
*/
|
| 641 |
function metrics_metric_delete($property_id) {
|
| 642 |
db_query('DELETE FROM {metrics_property} WHERE property_id = %d', $property_id);
|
| 643 |
db_query('DELETE FROM {metrics_property_node_types} WHERE property_id = %d', $property_id);
|
| 644 |
db_query('DELETE FROM {metrics_property_result} WHERE property_id = %d', $property_id);
|
| 645 |
db_query('DELETE FROM {metrics_metric} WHERE property_id = %d', $property_id);
|
| 646 |
db_query('DELETE FROM {metrics_ranking} WHERE property_id = %d', $property_id);
|
| 647 |
}
|
| 648 |
|
| 649 |
|
| 650 |
|
| 651 |
|
| 652 |
/**
|
| 653 |
* Property metrics form.
|
| 654 |
*/
|
| 655 |
function metrics_metrics_form($edit = array()) {
|
| 656 |
if (isset($edit['name'])) {
|
| 657 |
drupal_set_title(check_plain($edit['name']));
|
| 658 |
}
|
| 659 |
|
| 660 |
$function_options = array(0 => '<Select One>');
|
| 661 |
foreach (_metrics_get_metrics_functions() as $function) {
|
| 662 |
$info = $function('info');
|
| 663 |
$function_options[$function] = $info['name'];
|
| 664 |
}
|
| 665 |
|
| 666 |
$form['property_id'] = array(
|
| 667 |
'#type' => 'value',
|
| 668 |
'#value' => $edit['property_id'],
|
| 669 |
);
|
| 670 |
$form['metrics'] = array(
|
| 671 |
'#tree' => TRUE,
|
| 672 |
);
|
| 673 |
foreach ((array) $edit['metrics'] as $key => $metric) {
|
| 674 |
$form['metrics'][$key]['delete'] = array(
|
| 675 |
'#type' => 'checkbox',
|
| 676 |
);
|
| 677 |
|
| 678 |
if ($info = metrics_call_metric($metric, 'info')) {
|
| 679 |
$form['metrics'][$key]['function_name'] = array(
|
| 680 |
'#type' => 'item',
|
| 681 |
'#value' => $info['name'],
|
| 682 |
'#description' => $info['description'],
|
| 683 |
);
|
| 684 |
}
|
| 685 |
else {
|
| 686 |
$form['metrics'][$key]['function_name'] = array(
|
| 687 |
'#type' => 'item',
|
| 688 |
'#value' => t('Missing function: %name', array('%name' => $metric['function_name'])),
|
| 689 |
);
|
| 690 |
}
|
| 691 |
|
| 692 |
$form['metrics'][$key]['multiplier'] = array(
|
| 693 |
'#type' => 'textfield',
|
| 694 |
'#default_value' => $metric['multiplier'],
|
| 695 |
'#size' => 10,
|
| 696 |
'#required' => TRUE,
|
| 697 |
);
|
| 698 |
$form['metrics'][$key]['displayed'] = array(
|
| 699 |
'#type' => 'checkbox',
|
| 700 |
'#default_value' => $metric['displayed'],
|
| 701 |
);
|
| 702 |
// Allow the metric to put options on to the form.
|
| 703 |
$form['metrics'][$key]['options'] = metrics_call_metric($metric, 'options');
|
| 704 |
}
|
| 705 |
|
| 706 |
// If there's nothing in the select other than <Select One> then don't bother
|
| 707 |
// letting them add a new function.
|
| 708 |
if (count($function_options) > 1) {
|
| 709 |
$form['metrics']['new']['function_name'] = array(
|
| 710 |
'#type' => 'select',
|
| 711 |
'#options' => $function_options,
|
| 712 |
);
|
| 713 |
$form['metrics']['new']['multiplier'] = array(
|
| 714 |
'#type' => 'textfield',
|
| 715 |
'#default_value' => '1.0',
|
| 716 |
'#size' => 10,
|
| 717 |
'#required' => TRUE,
|
| 718 |
);
|
| 719 |
$form['metrics']['new']['displayed'] = array(
|
| 720 |
'#type' => 'checkbox',
|
| 721 |
'#default_value' => TRUE,
|
| 722 |
);
|
| 723 |
}
|
| 724 |
|
| 725 |
$form['submit'] = array(
|
| 726 |
'#type' => 'submit',
|
| 727 |
'#value' => t('Submit'),
|
| 728 |
);
|
| 729 |
return $form;
|
| 730 |
}
|
| 731 |
|
| 732 |
function theme_metrics_metrics_form(&$form) {
|
| 733 |
$header = array(t('Delete'), t('Function'), t('Multiplier'), t('Displayed'), t('Options'));
|
| 734 |
foreach (element_children($form['metrics']) as $key) {
|
| 735 |
$row = array();
|
| 736 |
$row[] = drupal_render($form['metrics'][$key]['delete']);
|
| 737 |
$row[] = drupal_render($form['metrics'][$key]['function_name']);
|
| 738 |
$row[] = drupal_render($form['metrics'][$key]['multiplier']);
|
| 739 |
$row[] = drupal_render($form['metrics'][$key]['displayed']);
|
| 740 |
$row[] = drupal_render($form['metrics'][$key]['options']);
|
| 741 |
$rows[] = $row;
|
| 742 |
}
|
| 743 |
$output .= theme('table', $header, $rows);
|
| 744 |
$output .= drupal_render($form);
|
| 745 |
|
| 746 |
return $output;
|
| 747 |
}
|
| 748 |
|
| 749 |
function metrics_metrics_form_submit($form_id, $form_values) {
|
| 750 |
if (empty($form_values['property_id'])) {
|
| 751 |
return;
|
| 752 |
}
|
| 753 |
|
| 754 |
$recompute = FALSE;
|
| 755 |
|
| 756 |
// Add the metrics.
|
| 757 |
foreach ($form_values['metrics'] as $metric_id => $metric) {
|
| 758 |
if ($metric_id == 'new') {
|
| 759 |
if (!empty($metric['function_name'])) {
|
| 760 |
$metric_id = db_next_id('{metrics_metric}_metric_id');
|
| 761 |
db_query("INSERT INTO {metrics_metric} (metric_id, property_id, function_name, options, displayed, multiplier) VALUES (%d, %d, '%s', '%s', %d, %f)", $metric_id, $form_values['property_id'], $metric['function_name'], serialize(array()), $metric['displayed'], $metric['multiplier']);
|
| 762 |
$recompute = TRUE;
|
| 763 |
}
|
| 764 |
}
|
| 765 |
else if ($metric['delete']) {
|
| 766 |
db_query("DELETE FROM {metrics_metric} WHERE metric_id = %d", $metric_id);
|
| 767 |
$recompute = TRUE;
|
| 768 |
}
|
| 769 |
else {
|
| 770 |
db_query("UPDATE {metrics_metric} SET displayed = %d, options = '%s', multiplier = %f WHERE metric_id = %d", $metric['displayed'], serialize($metric['options']), $metric['multiplier'], $metric_id);
|
| 771 |
$recompute = TRUE;
|
| 772 |
}
|
| 773 |
}
|
| 774 |
|
| 775 |
if ($recompute) {
|
| 776 |
// Mark all these results as stale so they're recomputed.
|
| 777 |
db_query("UPDATE {metrics_property_result} SET timestamp = 0 WHERE property_id = %d", $form_values['property_id']);
|
| 778 |
drupal_set_message(t('On the next cron run @count rows will need to be re-computed.', array('@count' => db_affected_rows())));
|
| 779 |
}
|
| 780 |
}
|
| 781 |
|
| 782 |
|
| 783 |
/**
|
| 784 |
* Property ranking form.
|
| 785 |
*/
|
| 786 |
function metrics_rankings_form($edit = array()) {
|
| 787 |
if (isset($edit['name'])) {
|
| 788 |
drupal_set_title(check_plain($edit['name']));
|
| 789 |
}
|
| 790 |
|
| 791 |
$form['property_id'] = array(
|
| 792 |
'#type' => 'value',
|
| 793 |
'#value' => $edit['property_id'],
|
| 794 |
);
|
| 795 |
$form['rankings']['#tree'] = TRUE;
|
| 796 |
foreach ((array) $edit['rankings'] as $ranking_id => $ranking) {
|
| 797 |
$form['rankings'][$ranking_id]['delete'] = array(
|
| 798 |
'#type' => 'checkbox',
|
| 799 |
);
|
| 800 |
$form['rankings'][$ranking_id]['name'] = array(
|
| 801 |
'#type' => 'textfield',
|
| 802 |
'#default_value' => $ranking['name'],
|
| 803 |
'#size' => 15,
|
| 804 |
'#required' => TRUE,
|
| 805 |
);
|
| 806 |
$form['rankings'][$ranking_id]['threshold'] = array(
|
| 807 |
'#type' => 'textfield',
|
| 808 |
'#default_value' => $ranking['threshold'],
|
| 809 |
'#size' => 5,
|
| 810 |
'#required' => TRUE,
|
| 811 |
);
|
| 812 |
$form['rankings'][$ranking_id]['description'] = array(
|
| 813 |
'#type' => 'textfield',
|
| 814 |
'#default_value' => $ranking['description'],
|
| 815 |
'#size' => 20,
|
| 816 |
);
|
| 817 |
}
|
| 818 |
|
| 819 |
$form['rankings']['new']['name'] = array(
|
| 820 |
'#type' => 'textfield',
|
| 821 |
'#size' => 15,
|
| 822 |
);
|
| 823 |
$form['rankings']['new']['threshold'] = array(
|
| 824 |
'#type' => 'textfield',
|
| 825 |
'#size' => 5,
|
| 826 |
);
|
| 827 |
$form['rankings']['new']['description'] = array(
|
| 828 |
'#type' => 'textfield',
|
| 829 |
'#size' => 20,
|
| 830 |
);
|
| 831 |
|
| 832 |
$form['submit'] = array(
|
| 833 |
'#type' => 'submit',
|
| 834 |
'#value' => t('Submit')
|
| 835 |
);
|
| 836 |
return $form;
|
| 837 |
}
|
| 838 |
|
| 839 |
function theme_metrics_rankings_form(&$form) {
|
| 840 |
$header = array(t('Remove'), t('Name'), t('Threshold'), t('Description'));
|
| 841 |
foreach (element_children($form['rankings']) as $key) {
|
| 842 |
$row = array();
|
| 843 |
$row[] = drupal_render($form['rankings'][$key]['delete']);
|
| 844 |
$row[] = drupal_render($form['rankings'][$key]['name']);
|
| 845 |
$row[] = drupal_render($form['rankings'][$key]['threshold']);
|
| 846 |
$row[] = drupal_render($form['rankings'][$key]['description']);
|
| 847 |
$rows[] = $row;
|
| 848 |
}
|
| 849 |
$output .= theme('table', $header, $rows);
|
| 850 |
$output .= drupal_render($form);
|
| 851 |
|
| 852 |
return $output;
|
| 853 |
}
|
| 854 |
|
| 855 |
function metrics_rankings_form_submit($form_id, $form_values) {
|
| 856 |
if (empty($form_values['property_id'])) {
|
| 857 |
return;
|
| 858 |
}
|
| 859 |
|
| 860 |
$recompute = FALSE;
|
| 861 |
|
| 862 |
// Add the rankings.
|
| 863 |
foreach ($form_values['rankings'] as $ranking_id => $ranking) {
|
| 864 |
if ($ranking_id == 'new') {
|
| 865 |
if (!empty($ranking['name'])) {
|
| 866 |
$ranking_id = db_next_id('{metrics_ranking}_ranking_id');
|
| 867 |
db_query("INSERT INTO {metrics_ranking} (ranking_id, property_id, name, threshold, description) VALUES (%d, %d, '%s', %d, '%s')", $ranking_id, $form_values['property_id'], $ranking['name'], $ranking['threshold'], $ranking['description']);
|
| 868 |
$recompute = TRUE;
|
| 869 |
}
|
| 870 |
}
|
| 871 |
else if ($ranking['delete']) {
|
| 872 |
db_query("DELETE FROM {metrics_ranking} WHERE ranking_id = %d", $ranking_id);
|
| 873 |
$recompute = TRUE;
|
| 874 |
}
|
| 875 |
else {
|
| 876 |
db_query("UPDATE {metrics_ranking} SET name = '%s', threshold = %d, description = '%s' WHERE ranking_id = %d", $ranking['name'], $ranking['threshold'], $ranking['description'], $ranking_id);
|
| 877 |
$recompute = TRUE;
|
| 878 |
}
|
| 879 |
}
|
| 880 |
|
| 881 |
if ($recompute) {
|
| 882 |
// Mark all these results as stale so they're recomputed.
|
| 883 |
db_query("UPDATE {metrics_property_result} SET timestamp = 0 WHERE property_id = %d", $form_values['property_id']);
|
| 884 |
drupal_set_message(t('On the next cron run @count rows will need to be re-computed.', array('@count' => db_affected_rows())));
|
| 885 |
}
|
| 886 |
}
|
| 887 |
|
| 888 |
|
| 889 |
function metrics_metrics_functions() {
|
| 890 |
return array(
|
| 891 |
'_metrics_body_word_count',
|
| 892 |
);
|
| 893 |
}
|
| 894 |
|
| 895 |
|
| 896 |
function _metrics_body_word_count($op, $options = NULL, $node = NULL) {
|
| 897 |
switch ($op) {
|
| 898 |
case 'info':
|
| 899 |
return array(
|
| 900 |
'name' => t('Metrics: Node body length'),
|
| 901 |
'description' => t('If there are more than the specified number of words, returns 1.'),
|
| 902 |
);
|
| 903 |
|
| 904 |
case 'compute':
|
| 905 |
$wc = explode(' ', $node->body);
|
| 906 |
if ($wc > $options['words']) {
|
| 907 |
return array(
|
| 908 |
'value' => 1,
|
| 909 |
'description' => t('The node body is longer than @count words.', array('@count' => $options['words'])),
|
| 910 |
);
|
| 911 |
}
|
| 912 |
else {
|
| 913 |
return array(
|
| 914 |
'value' => 0,
|
| 915 |
'description' => t('The node description is too short.', array('@count' => $options['words'])),
|
| 916 |
);
|
| 917 |
}
|
| 918 |
|
| 919 |
case 'options':
|
| 920 |
$limit = drupal_map_assoc(array(5, 10, 15, 20, 25, 50, 100, 200, 300, 500));
|
| 921 |
$form['words'] = array(
|
| 922 |
'#type' => 'select',
|
| 923 |
'#title' => t('Word count'),
|
| 924 |
'#default_value' => isset($options['words']) ? $options['words'] : 100,
|
| 925 |
'#options' => $limit,
|
| 926 |
);
|
| 927 |
return $form;
|
| 928 |
}
|
| 929 |
}
|