annotate report/ad_report.module @ 1:948362c2a207 ad

update advertisement
author pierre
date Thu, 02 Apr 2009 15:28:21 +0000
parents d8a3998dac8e
children 6aeff3329e01
rev   line source
pierre@0 1 <?php
pierre@1 2 // $Id: ad_report.module,v 1.1.2.3.2.7.2.6.2.7 2009/03/27 19:31:36 jeremy Exp $
pierre@0 3
pierre@0 4 /**
pierre@0 5 * @file
pierre@0 6 * Provides comprehensive charts and reports about advertising statistics.
pierre@0 7 *
pierre@1 8 * Copyright (c) 2007-2009.
pierre@0 9 * Jeremy Andrews <jeremy@tag1consulting.com>.
pierre@0 10 */
pierre@0 11
pierre@0 12 /**
pierre@0 13 * Implementation of hook_menu().
pierre@0 14 */
pierre@0 15 function ad_report_menu() {
pierre@0 16 $items = array();
pierre@1 17 $items['admin/content/ad/report'] = array(
pierre@1 18 'title' => t('Reports'),
pierre@1 19 'page callback' => 'drupal_get_form',
pierre@1 20 'page arguments' => array('ad_report_admin'),
pierre@1 21 'access arguments' => array('generate administrative reports'),
pierre@1 22 'type' => MENU_LOCAL_TASK,
pierre@1 23 'weight' => 1
pierre@1 24 );
pierre@1 25 $items['admin/content/ad/report/display'] = array(
pierre@1 26 'page callback' => 'ad_report_admin_display',
pierre@1 27 'access arguments' => array('generate administrative reports'),
pierre@1 28 'type' => MENU_CALLBACK
pierre@1 29 );
pierre@1 30 $items['admin/content/ad/report/csv'] = array(
pierre@1 31 'page callback' => 'ad_report_admin_ad_table',
pierre@1 32 'page arguments' => array('0', '0', array(), TRUE),
pierre@1 33 'access arguments' => array('generate administrative reports'),
pierre@1 34 'type' => MENU_CALLBACK
pierre@1 35 );
pierre@0 36 $items['node/%node/report'] = array(
pierre@1 37 'title' => t('Reports'),
pierre@1 38 'page callback' => 'ad_report_bargraph_handler',
pierre@0 39 'page arguments' => array(1),
pierre@0 40 'type' => MENU_LOCAL_TASK,
pierre@1 41 'access callback' => 'ad_report_bargraph_access',
pierre@0 42 'access arguments' => array(1),
pierre@0 43 );
pierre@1 44 $items['ad_report/%node/bargraph/node/%/%'] = array(
pierre@1 45 'title' => 'Bar graph',
pierre@1 46 'page callback' => 'ad_report_generate_bargraph',
pierre@1 47 'page arguments' => array(1, 'node', 4, 5),
pierre@1 48 'type' => MENU_CALLBACK,
pierre@1 49 'access callback' => 'ad_report_bargraph_access',
pierre@0 50 'access arguments' => array(1),
pierre@0 51 );
pierre@1 52 return $items;
pierre@1 53 }
pierre@1 54
pierre@1 55 /**
pierre@1 56 * Drupal hook_perm implementation.
pierre@1 57 */
pierre@1 58 function ad_report_perm() {
pierre@1 59 return array(t('generate administrative reports'));
pierre@1 60 }
pierre@1 61
pierre@1 62 /**
pierre@1 63 * Menu system callback, determine if current user can generate reports.
pierre@1 64 */
pierre@1 65 function ad_report_bargraph_access($node) {
pierre@1 66 if (isset($node->adtype)) {
pierre@1 67 return ad_permission($node->nid, 'generate reports');
pierre@1 68 }
pierre@1 69 }
pierre@1 70
pierre@1 71 /**
pierre@1 72 *
pierre@1 73 */
pierre@1 74 function ad_report_bargraph_handler($node) {
pierre@1 75 return ad_report_bargraph($node, "node/$node->nid/report", 'node', arg(3), arg(4));
pierre@1 76 }
pierre@1 77
pierre@1 78 /**
pierre@1 79 * Ad module hook_adapi.
pierre@1 80 */
pierre@1 81 function ad_report_adapi($op, $node = NULL) {
pierre@1 82 switch ($op) {
pierre@1 83 case 'permissions':
pierre@1 84 return array(
pierre@1 85 'generate reports' => TRUE,
pierre@1 86 );
pierre@1 87 }
pierre@1 88 }
pierre@1 89
pierre@1 90 /**
pierre@1 91 *
pierre@1 92 */
pierre@1 93 function ad_report_admin() {
pierre@1 94 $form = array();
pierre@1 95
pierre@1 96 $start = isset($_SESSION['ad_report_start']) ? strtotime($_SESSION['ad_report_start']) : _ad_report_first_day_of_month();
pierre@1 97 $end = isset($_SESSION['ad_report_end']) ? strtotime($_SESSION['ad_report_end']) : _ad_report_last_day_of_month();
pierre@1 98 $group = isset($_SESSION['ad_report_group']) ? $_SESSION['ad_report_group'] : array('all');
pierre@1 99
pierre@1 100 $form['dates'] = array(
pierre@1 101 '#type' => 'fieldset',
pierre@1 102 '#title' => t('Report dates'),
pierre@1 103 '#prefix' => '<div class="container-inline">',
pierre@1 104 '#suffix' => '</div>',
pierre@0 105 );
pierre@1 106 $form['dates']['start'] = array(
pierre@1 107 '#type' => 'textfield',
pierre@1 108 '#title' => t('Start'),
pierre@1 109 '#size' => 24,
pierre@1 110 '#maxlength' => 64,
pierre@1 111 '#default_value' => _ad_report_format_date_human($start),
pierre@1 112 // display pop up calendar if jstools jscalendar module enabled
pierre@1 113 '#attributes' => array('class' => 'jscalendar'),
pierre@1 114 '#jscalendar_ifFormat' => '%Y-%m-%d %H:%M',
pierre@1 115 '#jscalendar_timeFormat' => '24',
pierre@0 116 );
pierre@1 117 $form['dates']['space1'] = array(
pierre@1 118 '#value' => '&nbsp;&nbsp;',
pierre@1 119 );
pierre@1 120 $form['dates']['end'] = array(
pierre@1 121 '#type' => 'textfield',
pierre@1 122 '#title' => t('End'),
pierre@1 123 '#size' => 24,
pierre@1 124 '#maxlength' => 64,
pierre@1 125 '#default_value' => _ad_report_format_date_human($end),
pierre@1 126 // display pop up calendar if jstools jscalendar module enabled
pierre@1 127 '#attributes' => array('class' => 'jscalendar'),
pierre@1 128 '#jscalendar_ifFormat' => '%Y-%m-%d %H:%M',
pierre@1 129 );
pierre@1 130 $form['dates']['space2'] = array(
pierre@1 131 '#value' => '&nbsp;&nbsp;&nbsp;',
pierre@0 132 );
pierre@0 133
pierre@1 134 // groups
pierre@1 135 $groups = ad_groups_list();
pierre@1 136 $form['groups'] = array(
pierre@1 137 '#type' => 'fieldset',
pierre@1 138 '#title' => t('Groups'),
pierre@1 139 );
pierre@1 140 $options = array();
pierre@1 141 $options['all'] = t('- All -');
pierre@1 142 $options = $options + $groups;
pierre@1 143 $form['groups']['group'] = array(
pierre@1 144 '#type' => 'select',
pierre@1 145 '#title' => t('Ad groups'),
pierre@1 146 '#options' => $options,
pierre@1 147 '#multiple' => TRUE,
pierre@1 148 '#required' => TRUE,
pierre@1 149 '#default_value' => $group,
pierre@1 150 );
pierre@1 151
pierre@1 152 // submit
pierre@1 153 $form['submit'] = array(
pierre@1 154 '#type' => 'submit',
pierre@1 155 '#value' => t('Generate report'),
pierre@1 156 '#weight' => 10,
pierre@1 157 );
pierre@1 158 $form['reset'] = array(
pierre@1 159 '#type' => 'submit',
pierre@1 160 '#value' => t('Reset report'),
pierre@1 161 '#weight' => 10,
pierre@1 162 );
pierre@1 163
pierre@1 164 return $form;
pierre@0 165 }
pierre@1 166
pierre@0 167 /**
pierre@1 168 * Sanity check the date range.
pierre@1 169 */
pierre@1 170 function ad_report_admin_validate($form, $form_state) {
pierre@1 171 if ($form_state['clicked_button']['#value'] == t('Reset report')) {
pierre@1 172 unset($_SESSION['ad_report_start']);
pierre@1 173 unset($_SESSION['ad_report_end']);
pierre@1 174 unset($_SESSION['ad_report_group']);
pierre@1 175 }
pierre@1 176 else {
pierre@1 177 $start = isset($form_state['values']['start']) ? strtotime($form_state['values']['start']) : 0;
pierre@1 178 $end = isset($form_state['values']['start']) ? strtotime($form_state['values']['end']) : 0;
pierre@1 179 if (!$start) {
pierre@1 180 form_set_error('start', t('You must enter a valid start date.'));
pierre@1 181 }
pierre@1 182 else if ($start >= (time() - 3600)) {
pierre@1 183 form_set_error('start', t('The report must start at least one hour before the current time.'));
pierre@1 184 }
pierre@1 185 else if ($start >= $end) {
pierre@1 186 form_set_error('start', t('The report must start before it ends.'));
pierre@1 187 }
pierre@1 188 if (!$end) {
pierre@1 189 form_set_error('end', t('You must enter a valid end date.'));
pierre@1 190 }
pierre@1 191 }
pierre@0 192 }
pierre@1 193
pierre@1 194 /**
pierre@1 195 * Redirect to a path to generate the requested report.
pierre@1 196 */
pierre@1 197 function ad_report_admin_submit($form, $form_state) {
pierre@1 198 if ($form_state['clicked_button']['#value'] == t('Generate report')) {
pierre@1 199 $start = date('YmdHi', strtotime($form_state['values']['start']));
pierre@1 200 $end = date('YmdHi', strtotime($form_state['values']['end']));
pierre@1 201 $group = $form_state['values']['group'];
pierre@1 202 $_SESSION['ad_report_start'] = $start;
pierre@1 203 $_SESSION['ad_report_end'] = $end;
pierre@1 204 $_SESSION['ad_report_group'] = $group;
pierre@1 205
pierre@1 206 drupal_goto('admin/content/ad/report/display');
pierre@1 207 }
pierre@1 208 }
pierre@1 209
pierre@1 210 /**
pierre@1 211 * Display the administrative report.
pierre@1 212 */
pierre@1 213 function ad_report_admin_display() {
pierre@1 214 $start = isset($_SESSION['ad_report_start']) ? $_SESSION['ad_report_start'] : 0;
pierre@1 215 $end = isset($_SESSION['ad_report_end']) ? $_SESSION['ad_report_end'] : 0;
pierre@1 216 $group = isset($_SESSION['ad_report_group']) ? $_SESSION['ad_report_group'] : array();
pierre@1 217 if (!$start && !$end) {
pierre@1 218 drupal_goto('admin/content/ad/report');
pierre@1 219 }
pierre@1 220 $output = '<div class="image"><img src="'. url("ad_report/0/bargraph/admin/$start/$end") .'" /></div>';
pierre@1 221 $output .= ad_report_admin_ad_table(strtotime($start), strtotime($end), $group);
pierre@1 222 $output .= '<div>'. l(t('Modify report'), 'admin/content/ad/report') .'</div>';
pierre@1 223 return $output;
pierre@1 224 }
pierre@1 225
pierre@1 226 /**
pierre@1 227 *
pierre@1 228 */
pierre@1 229 function ad_report_admin_ad_table($start = 0, $end = 0, $group = array(), $csv = FALSE) {
pierre@1 230 if (!$start) {
pierre@1 231 $start = isset($_SESSION['ad_report_start']) ? strtotime($_SESSION['ad_report_start']) : 0;
pierre@1 232 }
pierre@1 233 if (!$end) {
pierre@1 234 $end = isset($_SESSION['ad_report_end']) ? strtotime($_SESSION['ad_report_end']) : 0;
pierre@1 235 }
pierre@1 236 if (empty($group)) {
pierre@1 237 $group = isset($_SESSION['ad_report_group']) ? $_SESSION['ad_report_group'] : array();
pierre@1 238 }
pierre@1 239 // prepare dates
pierre@1 240 $start = _ad_report_format_date_db($start);
pierre@1 241 $end = _ad_report_format_date_db($end);
pierre@1 242
pierre@1 243 // prepare groups
pierre@1 244 $groups = ad_groups_list();
pierre@1 245 $all = FALSE;
pierre@1 246 $none = FALSE;
pierre@1 247 if (is_array($group)) {
pierre@1 248 if (in_array('all', $group)) {
pierre@1 249 $all = TRUE;
pierre@1 250 }
pierre@1 251 if (!$all) {
pierre@1 252 if (sizeof($group) == sizeof($groups)) {
pierre@1 253 $all = TRUE;
pierre@1 254 }
pierre@1 255 }
pierre@1 256 if (in_array('0', $group)) {
pierre@1 257 unset($group[0]);
pierre@1 258 $none = TRUE;
pierre@1 259 }
pierre@1 260 }
pierre@1 261
pierre@1 262 $select = 'SELECT DISTINCT(aid) as nid FROM {ad_statistics} a';
pierre@1 263 if ($all) {
pierre@1 264 $where = array(
pierre@1 265 "a.action = 'view'",
pierre@1 266 'a.date >= %d',
pierre@1 267 'a.date <= %d',
pierre@1 268 'a.aid > 0',
pierre@1 269 );
pierre@1 270 $join = array();
pierre@1 271 $args = array($start, $end);
pierre@1 272 }
pierre@1 273 else if ($none) {
pierre@1 274 if (sizeof($group)) {
pierre@1 275 $where = array(
pierre@1 276 '(t.tid IN (%s) OR ISNULL(t.tid))',
pierre@1 277 "a.action = 'view'",
pierre@1 278 'a.date >= %d',
pierre@1 279 'a.date <= %d',
pierre@1 280 );
pierre@1 281 $join = array(
pierre@1 282 'LEFT JOIN {term_node} t ON a.aid = t.tid',
pierre@1 283 );
pierre@1 284 $args = array(implode(',', $group), $start, $end);
pierre@1 285 }
pierre@1 286 else {
pierre@1 287 $where = array(
pierre@1 288 'ISNULL(t.tid)',
pierre@1 289 "a.action = 'view'",
pierre@1 290 'a.date >= %d',
pierre@1 291 'a.date <= %d',
pierre@1 292 );
pierre@1 293 $join = array(
pierre@1 294 'LEFT JOIN {term_node} t ON a.aid = t.tid',
pierre@1 295 );
pierre@1 296 $args = array($start, $end);
pierre@1 297 }
pierre@1 298 }
pierre@1 299 else {
pierre@1 300 $where = array(
pierre@1 301 't.tid IN (%s)',
pierre@1 302 "a.action = 'view'",
pierre@1 303 'a.date >= %d',
pierre@1 304 'a.date <= %d',
pierre@1 305 );
pierre@1 306 $join = array(
pierre@1 307 'LEFT JOIN {term_node} t ON a.aid = t.tid',
pierre@1 308 );
pierre@1 309 $args = array(implode(',', $group), $start, $end);
pierre@1 310 }
pierre@1 311
pierre@1 312 $return = module_invoke_all('adreport', $join, $where, $args, $select);
pierre@1 313 foreach ($return as $type => $value) {
pierre@1 314 switch ($type) {
pierre@1 315 case 'join':
pierre@1 316 if (is_array($value)) {
pierre@1 317 foreach ($value as $option) {
pierre@1 318 $join[] = $option;
pierre@1 319 }
pierre@1 320 }
pierre@1 321 break;
pierre@1 322 case 'where':
pierre@1 323 if (is_array($value)) {
pierre@1 324 foreach ($value as $option) {
pierre@1 325 $where[] = $option;
pierre@1 326 }
pierre@1 327 }
pierre@1 328 break;
pierre@1 329 case 'args':
pierre@1 330 if (is_array($value)) {
pierre@1 331 foreach ($value as $option) {
pierre@1 332 $args[] = $option;
pierre@1 333 }
pierre@1 334 }
pierre@1 335 break;
pierre@1 336 }
pierre@1 337 }
pierre@1 338
pierre@1 339 // Build the query.
pierre@1 340 $query = $select .' '. implode(' ', $join) .' WHERE '. implode(' AND ', $where);
pierre@1 341 $ads = array();
pierre@1 342 $result = db_query($query, $args);
pierre@1 343 while ($ad = db_fetch_object($result)) {
pierre@1 344 if ($ad->nid) {
pierre@1 345 $ads[$ad->nid] = $ad->nid;
pierre@1 346 }
pierre@1 347 }
pierre@1 348
pierre@1 349 if ($csv) {
pierre@1 350 header('Content-type: application/octet-stream');
pierre@1 351 header("Content-Disposition: attachment; filename=report-$start-$end.csv");
pierre@1 352 echo "ad id, title, first view, last view, clicks, views, click-thru\n";
pierre@1 353 }
pierre@1 354 else {
pierre@1 355 $output = '<div class="describe">' . t('There !count matching your parameters.', array('!count' => format_plural(sizeof($ads), 'was 1 active ad', 'were @count active ads'))) . '</div>';
pierre@1 356
pierre@1 357 $headers = array(t('Advertisement'), t('Active dates'), t('Views'), t('Clicks'), t('Click-thru'));
pierre@1 358 // get counts for each ad
pierre@1 359 $rows = array();
pierre@1 360 }
pierre@1 361 $total_views = $total_clicks = 0;
pierre@1 362 foreach ($ads as $nid) {
pierre@1 363 $ad = node_load($nid);
pierre@1 364 if ($ad->nid) {
pierre@1 365 $views = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $nid, $start, $end));
pierre@1 366 $first = _ad_report_get_date_from_path((int)db_result(db_query("SELECT MIN(date) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $nid, $start, $end)));
pierre@1 367 $first = format_date($first, 'small');
pierre@1 368 $last = _ad_report_get_date_from_path((int)db_result(db_query("SELECT MAX(date) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $nid, $start, $end)));
pierre@1 369 $last = format_date($last, 'small');
pierre@1 370 $clicks = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d AND date <= %d", $nid, $start, $end));
pierre@1 371 if ($views) {
pierre@1 372 $clickthru = number_format($clicks / $views, 2) .'%';
pierre@1 373 }
pierre@1 374 else {
pierre@1 375 $clickthru = '0%';
pierre@1 376 }
pierre@1 377 if ($views || $clicks) {
pierre@1 378 if ($csv) {
pierre@1 379 echo "$ad->nid, $ad->title, $first, $last, $views, $clicks, $clickthru\n";
pierre@1 380 }
pierre@1 381 else {
pierre@1 382 $row = array();
pierre@1 383 $row[] = l($ad->title, "node/$ad->nid");
pierre@1 384 $row[] = "first view: $first<br />last view: $last";
pierre@1 385 $row[] = number_format($views);
pierre@1 386 $row[] = number_format($clicks);
pierre@1 387 $row[] = $clickthru;
pierre@1 388 $rows[] = $row;
pierre@1 389 $total_views += $views;
pierre@1 390 $total_clicks += $clicks;
pierre@1 391 }
pierre@1 392 }
pierre@1 393 }
pierre@1 394 }
pierre@1 395 if ($csv) {
pierre@1 396 return (0);
pierre@1 397 }
pierre@1 398 if ($total_views || $total_clicks) {
pierre@1 399 $row = array();
pierre@1 400 $row[] = '<strong>'. t('Total') .'</strong>';
pierre@1 401 $row[] = '';
pierre@1 402 $row[] = '<strong>'. number_format($total_views) .'</strong>';
pierre@1 403 $row[] = '<strong>'. number_format($total_clicks) .'</strong>';
pierre@1 404 if ($total_views) {
pierre@1 405 $row[] = '<strong>'. number_format($total_clicks / $total_views, 2) .'%' .'</strong>';
pierre@1 406 }
pierre@1 407 else {
pierre@1 408 $row[] = '<strong>'. '0%' .'</strong>';
pierre@1 409 }
pierre@1 410 $rows[] = $row;
pierre@1 411 }
pierre@1 412 $output .= theme('table', $headers, $rows);
pierre@1 413 $output .= l(t('Download CSV'), 'admin/content/ad/report/csv');
pierre@1 414 return $output;
pierre@1 415 }
pierre@1 416
pierre@1 417 /**
pierre@1 418 * Returns a timestamp for the first hour of the first day of the month.
pierre@1 419 */
pierre@1 420 function _ad_report_first_day_of_month($time = NULL) {
pierre@1 421 if ($time === NULL) {
pierre@1 422 $time = time();
pierre@1 423 }
pierre@1 424 return strtotime(date('Ym010000', $time));
pierre@1 425 }
pierre@1 426
pierre@1 427 /**
pierre@1 428 * Returns a timestamp for the last hour of the last day of the month.
pierre@1 429 */
pierre@1 430 function _ad_report_last_day_of_month($time = NULL) {
pierre@1 431 if ($time === NULL) {
pierre@1 432 $time = time();
pierre@1 433 }
pierre@1 434 $month = date('m', $time);
pierre@1 435 $year = date('Y', $time);
pierre@1 436 $day = date('d', mktime(0, 0, 0, ($month + 1), 0, $year));
pierre@1 437 return strtotime("{$year}{$month}{$day}2359");
pierre@1 438 }
pierre@1 439
pierre@0 440 /**
pierre@0 441 * Page to display ad with bargraph.
pierre@0 442 */
pierre@1 443 function ad_report_bargraph($data, $url, $type = 'node', $start = 0, $end = 0) {
pierre@1 444 if ($type == 'node') {
pierre@1 445 drupal_set_title($data->title);
pierre@0 446 }
pierre@1 447 $start_date = _ad_report_get_date_from_path($start);
pierre@1 448 $end_date = _ad_report_get_date_from_path($end);
pierre@1 449 $output = drupal_get_form('ad_report_range_form', $type, $url, $start_date, $end_date);
pierre@1 450 if ($start && $end) {
pierre@1 451 switch ($type) {
pierre@1 452 case 'node':
pierre@1 453 $ad = db_fetch_object(db_query('SELECT aid, redirect, adtype FROM {ads} WHERE aid = %d', $data->nid));
pierre@1 454 if ($ad->aid) {
pierre@1 455 $output .= '<img src="'. url("ad_report/$data->nid/bargraph/node/$start/$end") .'" />';
pierre@1 456 $output .= theme('box', '', module_invoke("ad_$data->adtype", 'display_ad', $ad));
pierre@1 457 $output .= ad_report_group_table($data->nid, $type, $start, $end);
pierre@1 458 }
pierre@1 459 $output .= module_invoke('ad', 'click_history', $data->nid);
pierre@1 460 break;
pierre@1 461 default:
pierre@1 462 $output = '<img src="'. url("ad_report/$data->uid/bargraph/$granularity/$type") .'" />';
pierre@1 463 break;
pierre@1 464 }
pierre@0 465 }
pierre@0 466 return $output;
pierre@0 467 }
pierre@0 468
pierre@0 469 /**
pierre@1 470 * Return a form for selecting a date range for generating a report.
pierre@1 471 */
pierre@1 472 function ad_report_range_form($form_state, $type, $url = NULL, $start = NULL, $end = NULL) {
pierre@1 473 $form = array();
pierre@1 474
pierre@1 475 $start = $start ? $start : _ad_report_first_day_of_month();
pierre@1 476 $end = $end ? $end : _ad_report_last_day_of_month();
pierre@1 477
pierre@1 478 $form['report'] = array(
pierre@1 479 '#type' => 'fieldset',
pierre@1 480 '#title' => t('Report dates'),
pierre@1 481 '#prefix' => '<div class="container-inline">',
pierre@1 482 '#suffix' => '</div>',
pierre@1 483 );
pierre@1 484 $form['report']['type'] = array(
pierre@1 485 '#value' => $type,
pierre@1 486 '#type' => 'hidden',
pierre@1 487 );
pierre@1 488 $form['report']['url'] = array(
pierre@1 489 '#value' => $url,
pierre@1 490 '#type' => 'hidden',
pierre@1 491 );
pierre@1 492 $form['report']['start'] = array(
pierre@1 493 '#type' => 'textfield',
pierre@1 494 '#title' => t('Start'),
pierre@1 495 '#size' => 24,
pierre@1 496 '#maxlength' => 64,
pierre@1 497 '#default_value' => _ad_report_format_date_human($start),
pierre@1 498 // display pop up calendar if jstools jscalendar module enabled
pierre@1 499 '#attributes' => array('class' => 'jscalendar'),
pierre@1 500 '#jscalendar_ifFormat' => '%Y-%m-%d %H:%M',
pierre@1 501 '#jscalendar_timeFormat' => '24',
pierre@1 502 );
pierre@1 503 $form['report']['space1'] = array(
pierre@1 504 '#value' => '&nbsp;&nbsp;',
pierre@1 505 );
pierre@1 506 $form['report']['end'] = array(
pierre@1 507 '#type' => 'textfield',
pierre@1 508 '#title' => t('End'),
pierre@1 509 '#size' => 24,
pierre@1 510 '#maxlength' => 64,
pierre@1 511 '#default_value' => _ad_report_format_date_human($end),
pierre@1 512 // display pop up calendar if jstools jscalendar module enabled
pierre@1 513 '#attributes' => array('class' => 'jscalendar'),
pierre@1 514 '#jscalendar_ifFormat' => '%Y-%m-%d %H:%M',
pierre@1 515 );
pierre@1 516 $form['report']['space2'] = array(
pierre@1 517 '#value' => '&nbsp;&nbsp;&nbsp;',
pierre@1 518 );
pierre@1 519 $form['report']['generate'] = array(
pierre@1 520 '#type' => 'submit',
pierre@1 521 '#value' => t('Generate report'),
pierre@1 522 );
pierre@1 523
pierre@1 524 return $form;
pierre@1 525 }
pierre@1 526
pierre@1 527 /**
pierre@1 528 * Validate the form range.
pierre@1 529 */
pierre@1 530 function ad_report_range_form_validate($form, $form_state) {
pierre@1 531 $start = isset($form_state['values']['start']) ? strtotime($form_state['values']['start']) : 0;
pierre@1 532 $end = isset($form_state['values']['start']) ? strtotime($form_state['values']['end']) : 0;
pierre@1 533 if (!$start) {
pierre@1 534 form_set_error('start', t('You must enter a valid start date.'));
pierre@1 535 }
pierre@1 536 else if ($start >= (time() - 3600)) {
pierre@1 537 form_set_error('start', t('The report must start at least one hour before the current time.'));
pierre@1 538 }
pierre@1 539 else if ($start >= $end) {
pierre@1 540 form_set_error('start', t('The report must start before it ends.'));
pierre@1 541 }
pierre@1 542 if (!$end) {
pierre@1 543 form_set_error('end', t('You must enter a valid end date.'));
pierre@1 544 }
pierre@1 545 }
pierre@1 546
pierre@1 547 /**
pierre@1 548 * Redirect to URL for displaying report.
pierre@1 549 */
pierre@1 550 function ad_report_range_form_submit($form, $form_state) {
pierre@1 551 $start = date('YmdHi', strtotime($form_state['values']['start']));
pierre@1 552 $end = date('YmdHi', strtotime($form_state['values']['end']));
pierre@1 553 drupal_goto($form_state['values']['url'] ."/$start/$end");
pierre@1 554 }
pierre@1 555
pierre@1 556 /**
pierre@1 557 * Helper function to extract date from URL.
pierre@1 558 */
pierre@1 559 function _ad_report_get_date_from_path($path) {
pierre@1 560 if (isset($path) && $path) {
pierre@1 561 $year = substr($path, 0, 4);
pierre@1 562 $month = substr($path, 4, 2);
pierre@1 563 $day = substr($path, 6, 2);
pierre@1 564 $hour = substr($path, 8, 2);
pierre@1 565 if (strlen($path) == 12) {
pierre@1 566 $minute = substr($path, 10, 2);
pierre@1 567 }
pierre@1 568 else {
pierre@1 569 $minute = 0;
pierre@1 570 }
pierre@1 571 $date = strtotime("$month/$day/$year $hour:$minute");
pierre@1 572 if ($date > 0) {
pierre@1 573 return $date;
pierre@1 574 }
pierre@1 575 drupal_set_message(t('Invalid date specified in range.'), 'error');
pierre@1 576 }
pierre@1 577 }
pierre@1 578
pierre@1 579 /**
pierre@1 580 * Helper function to format date.
pierre@1 581 */
pierre@1 582 function _ad_report_format_date_human($date) {
pierre@1 583 return date('Y-m-d H:i', $date);
pierre@1 584 }
pierre@1 585
pierre@1 586 /**
pierre@1 587 * Helper function to format date.
pierre@1 588 */
pierre@1 589 function _ad_report_format_date_db($date) {
pierre@1 590 return date('YmdH', $date);
pierre@1 591 }
pierre@1 592
pierre@1 593 /**
pierre@1 594 * Display table with per-group statistics.
pierre@1 595 */
pierre@1 596 function ad_report_group_table($id, $type, $start, $end) {
pierre@1 597 $start_date = _ad_report_format_date_db(_ad_report_get_date_from_path($start));
pierre@1 598 $end_date = _ad_report_format_date_db(_ad_report_get_date_from_path($end));
pierre@1 599 // TODO: Support other types than nodes
pierre@1 600 $result = db_query('SELECT DISTINCT(adgroup) FROM {ad_statistics} WHERE aid = %d AND date >= %d AND date <= %d', $id, $start_date, $end_date);
pierre@1 601 // extract all groups that this advertisement has been displayed in
pierre@1 602 while ($group = db_fetch_object($result)) {
pierre@1 603 if ($group->adgroup) {
pierre@1 604 $first = substr($group->adgroup, 0, 1);
pierre@1 605 if ($first == 't') {
pierre@1 606 $tids = $tids = explode(',', substr($group->adgroup, 1, strlen($group->adgroup)));
pierre@1 607 foreach ($tids as $tid) {
pierre@1 608 if ($tid) {
pierre@1 609 $adgroups[$tid][] = $group->adgroup;
pierre@1 610 }
pierre@1 611 }
pierre@1 612 }
pierre@1 613 else {
pierre@1 614 // handle this type of "group"
pierre@1 615 $adgroups['other'][] = $group->adgroup;
pierre@1 616 }
pierre@1 617 }
pierre@1 618 else {
pierre@1 619 $adgroups[0][] = $group->adgroup;
pierre@1 620 }
pierre@1 621 }
pierre@1 622 $headers = array(t('Group'), t('Active dates'), t('Views'), t('Clicks'), t('Click-thru'));
pierre@1 623 // get counts for each group
pierre@1 624 $groups = ad_groups_list();
pierre@1 625 $rows = array();
pierre@1 626 $total_views = $total_clicks = 0;
pierre@1 627 foreach ($groups as $tid => $group) {
pierre@1 628 $views = $clicks = 0;
pierre@1 629 if (isset($adgroups[$tid]) && is_array($adgroups[$tid])) {
pierre@1 630 foreach ($adgroups[$tid] as $adgroup) {
pierre@1 631 $views += (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND adgroup = '%s' AND action = 'view' AND date >= %d AND date <= %d", $id, $adgroup, $start_date, $end_date));
pierre@1 632 $clicks += (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND adgroup = '%s' AND action = 'click' AND date >= %d AND date <= %d", $id, $adgroup, $start_date, $end_date));
pierre@1 633 }
pierre@1 634 }
pierre@1 635 if ($views || $clicks) {
pierre@1 636 $begin = (int)db_result(db_query("SELECT MIN(date) FROM {ad_statistics} WHERE (adgroup LIKE '%%t%s' OR adgroup LIKE '%%,%s') AND action = 'view' AND date >= %d AND date <= %d", $tid, $tid, $start_date, $end_date));
pierre@1 637 if ($begin) {
pierre@1 638 $begin = format_date(_ad_report_get_date_from_path($begin), 'small');
pierre@1 639 $finish = (int)db_result(db_query("SELECT MAX(date) FROM {ad_statistics} WHERE (adgroup LIKE '%%t%s' OR adgroup LIKE '%%,%s') AND action = 'view' AND date >= %d AND date <= %d", $tid, $tid, $start_date, $end_date));
pierre@1 640 if ($finish) {
pierre@1 641 $finish = format_date(_ad_report_get_date_from_path($finish), 'small');
pierre@1 642 }
pierre@1 643 }
pierre@1 644 if ($begin && $finish) {
pierre@1 645 $row = array();
pierre@1 646 $row[] = $group;
pierre@1 647 $row[] = "first view: $begin<br />last view: $finish";
pierre@1 648 $row[] = number_format($views);
pierre@1 649 $row[] = number_format($clicks);
pierre@1 650 if ($views) {
pierre@1 651 $row[] = number_format($clicks / $views, 2) .'%';
pierre@1 652 }
pierre@1 653 else {
pierre@1 654 $row[] = '0%';
pierre@1 655 }
pierre@1 656 $rows[] = $row;
pierre@1 657 $total_views += $views;
pierre@1 658 $total_clicks += $clicks;
pierre@1 659 }
pierre@1 660 }
pierre@1 661 }
pierre@1 662 if ($total_views || $total_clicks) {
pierre@1 663 $row = array();
pierre@1 664 $row[] = '<strong>'. t('Total') .'</strong>';
pierre@1 665 $row[] = '';
pierre@1 666 $row[] = '<strong>'. number_format($total_views) .'</strong>';
pierre@1 667 $row[] = '<strong>'. number_format($total_clicks) .'</strong>';
pierre@1 668 if ($total_views) {
pierre@1 669 $row[] = '<strong>'. number_format($total_clicks / $total_views, 2) .'%' .'</strong>';
pierre@1 670 }
pierre@1 671 else {
pierre@1 672 $row[] = '<strong>'. '0%' .'</strong>';
pierre@1 673 }
pierre@1 674 $rows[] = $row;
pierre@1 675 }
pierre@1 676
pierre@1 677 return theme('table', $headers, $rows);
pierre@1 678 }
pierre@1 679
pierre@1 680 /**
pierre@0 681 * Page that utilizes gd to generate a bargraph.
pierre@0 682 */
pierre@1 683 function ad_report_generate_bargraph($id, $type, $start, $end) {
pierre@0 684 header("Content-type: image/png");
pierre@0 685
pierre@1 686 if ($type == 'node' && is_object($id)) {
pierre@1 687 $id = $id->nid;
pierre@1 688 }
pierre@1 689 $start = _ad_report_get_date_from_path($start);
pierre@1 690 $end = _ad_report_get_date_from_path($end);
pierre@1 691
pierre@1 692 // be sure we've been passed in valid parameters
pierre@1 693 $elapse = $end - $start;
pierre@1 694 if ($elapse <= 0 || $start <= 0 || $end <= 0) {
pierre@1 695 return NULL;
pierre@1 696 }
pierre@1 697 $increments = (int)($elapse / 3600);
pierre@1 698
pierre@1 699 // image size
pierre@1 700 $image_width = 700;
pierre@1 701 $image_height = 360;
pierre@1 702
pierre@1 703 // graph size
pierre@1 704 $graph_width = 600;
pierre@1 705 $graph_height = 250;
pierre@1 706 $graph_x_offset = 8;
pierre@1 707 $graph_y_offset = 8;
pierre@1 708 $graph_y = 8;
pierre@1 709
pierre@1 710 // calculate slices to extract from database
pierre@1 711 $width = $graph_width / $increments;
pierre@1 712 $number = $increments;
pierre@1 713 $factor = 1;
pierre@1 714 if ($width < 1) {
pierre@1 715 $factor = 1 / $width;
pierre@1 716 }
pierre@1 717 $number = $number / $factor;
pierre@1 718 $width = $width * $factor;
pierre@1 719 $slice = $elapse / $number;
pierre@1 720
pierre@1 721 // retrieve views and clicks from the database
pierre@0 722 $views = array();
pierre@1 723 $clicks = array();
pierre@0 724 $max_views = 0;
pierre@0 725 $max_clicks = 0;
pierre@1 726 $key = 0;
pierre@1 727 for ($i = $start; $i < $end; $i += $slice) {
pierre@1 728 $start_date = _ad_report_format_date_db($i);
pierre@1 729 $end_date = _ad_report_format_date_db($i + $slice);
pierre@1 730 switch ($type) {
pierre@1 731 case 'node':
pierre@1 732 $views[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'view' AND date >= %d AND date <= %d", $id, $start_date, $end_date));
pierre@1 733 $clicks[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE aid = %d AND action = 'click' AND date >= %d AND date <= %d", $id, $start_date, $end_date));
pierre@1 734 break;
pierre@1 735 case 'user':
pierre@1 736 $views[] = (int)db_result(db_query("SELECT SUM(a.count) FROM {ad_statistics} a LEFT JOIN {node} n ON a.aid = n.nid WHERE n.uid = %d AND n.type = 'ad' AND a.action = 'view' AND a.date >= %d AND a.date <= %d", $id, $start_date, $end_date));
pierre@1 737 $clicks[] = (int)db_result(db_query("SELECT SUM(a.count) FROM {ad_statistics} a LEFT JOIN {node} n ON a.aid = n.nid WHERE n.uid = %d AND n.type = 'ad' AND a.action = 'click' AND a.date >= %d AND a.date <= %d", $id, $start_date, $end_date));
pierre@1 738 break;
pierre@1 739 case 'admin':
pierre@1 740 $group = $_SESSION['ad_report_group'];
pierre@1 741 $all = FALSE;
pierre@1 742 $none = FALSE;
pierre@1 743 if (is_array($group)) {
pierre@1 744 if (in_array('all', $group)) {
pierre@1 745 $all = TRUE;
pierre@1 746 }
pierre@1 747 if (!$all) {
pierre@1 748 $groups = ad_groups_list();
pierre@1 749 if (sizeof($group) == sizeof($groups)) {
pierre@1 750 $all = TRUE;
pierre@1 751 }
pierre@1 752 }
pierre@1 753 if (in_array('0', $group)) {
pierre@1 754 unset($group[0]);
pierre@1 755 $none = TRUE;
pierre@1 756 }
pierre@1 757 }
pierre@1 758 if ($all) {
pierre@1 759 $views[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE action = 'view' AND date >= %d AND date <= %d", $start_date, $end_date));
pierre@1 760 $clicks[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} WHERE action = 'click' AND date >= %d AND date <= %d", $start_date, $end_date));
pierre@1 761 }
pierre@1 762 else if ($none) {
pierre@1 763 if (sizeof($group)) {
pierre@1 764 $views[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} a LEFT JOIN {term_node} t ON a.aid = t.tid WHERE (t.tid IN (%s) OR ISNULL(t.tid)) AND action = 'view' AND date >= %d AND date <= %d", implode(',', $group), $start_date, $end_date));
pierre@1 765 $clicks[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} a LEFT JOIN {term_node} t ON a.aid = t.tid WHERE (t.tid IN (%s) OR ISNULL(t.tid)) AND action = 'click' AND date >= %d AND date <= %d", implode(',', $group), $start_date, $end_date));
pierre@1 766 }
pierre@1 767 else {
pierre@1 768 $views[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} a LEFT JOIN {term_node} t ON a.aid = t.tid WHERE ISNULL(t.tid) AND action = 'view' AND date >= %d AND date <= %d", $start_date, $end_date));
pierre@1 769 $clicks[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} a LEFT JOIN {term_node} t ON a.aid = t.tid WHERE ISNULL(t.tid) AND action = 'click' AND date >= %d AND date <= %d", $start_date, $end_date));
pierre@1 770 }
pierre@1 771 }
pierre@1 772 else {
pierre@1 773 $views[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} a LEFT JOIN {term_node} t ON a.aid = t.tid WHERE tid IN (%s) AND action = 'view' AND date >= %d AND date <= %d", implode(',', $group), $start_date, $end_date));
pierre@1 774 $clicks[] = (int)db_result(db_query("SELECT SUM(count) FROM {ad_statistics} a LEFT JOIN {term_node} t ON a.aid = t.tid WHERE t.tid IN (%s) AND action = 'click' AND date >= %d AND date <= %d", implode(',', $group), $start_date, $end_date));
pierre@1 775 }
pierre@1 776 break;
pierre@1 777 default:
pierre@1 778 $function = "ad_report_views_$type";
pierre@1 779 if (function_exists("$function")) {
pierre@1 780 $views[] = $function($id, $day_start, $day_end);
pierre@1 781 }
pierre@1 782 $function = "ad_report_clicks_$type";
pierre@1 783 if (function_exists("$function")) {
pierre@1 784 $clicks[] = $function($id, $day_start, $day_end);
pierre@1 785 }
pierre@1 786 break;
pierre@1 787 }
pierre@1 788 $max_views = $views[$key] > $max_views ? $views[$key] : $max_views;
pierre@1 789 $max_clicks = $clicks[$key] > $max_clicks ? $clicks[$key] : $max_clicks;
pierre@1 790 $key++;
pierre@0 791 }
pierre@0 792
pierre@1 793 // create graph
pierre@0 794 $graph = imagecreate($image_width, $image_height);
pierre@0 795
pierre@1 796 // configure colors to use in chart
pierre@0 797 $color = array(
pierre@0 798 'white' => imagecolorallocate($graph, 255, 255, 255),
pierre@0 799 'black' => imagecolorallocate($graph, 0, 0, 0),
pierre@0 800 'grey' => imagecolorallocate($graph, 192, 192, 192),
pierre@0 801 'blue' => imagecolorallocate($graph, 0, 0, 255),
pierre@0 802 'orange' => imagecolorallocate($graph, 220, 210, 60),
pierre@1 803 'red' => imagecolorallocate($graph, 255, 0, 0),
pierre@0 804 );
pierre@0 805
pierre@1 806 // determine how big the spacers should be
pierre@1 807 $max = $max_views > $max_clicks ? $max_views : $max_clicks;
pierre@1 808 $y_map = ceil($max / $graph_y / $graph_y) * $graph_y;
pierre@1 809 $y_total = $y_map * $graph_y;
pierre@0 810
pierre@1 811 if ($y_total) {
pierre@1 812 // plot views and clicks on graph
pierre@1 813 foreach ($views as $key => $value) {
pierre@1 814 $view_height = $graph_height / $y_total * $value;
pierre@1 815 if ($view_height) {
pierre@1 816 imagefilledrectangle($graph, $graph_x_offset + $key * $width, $graph_y_offset + $graph_height - $view_height, $graph_x_offset + ($key + 1) * $width - 1, $graph_y_offset + $graph_height - 1, $color['blue']);
pierre@1 817 }
pierre@1 818 $click_height = $graph_height / $y_total * $clicks[$key];
pierre@1 819 if ($click_height) {
pierre@1 820 imagefilledrectangle($graph, $graph_x_offset + $key * $width, $graph_y_offset + $graph_height - $click_height, $graph_x_offset + ($key + 1) * $width - 1, $graph_y_offset + $graph_height - 1, $color['red']);
pierre@1 821 }
pierre@0 822 }
pierre@0 823 }
pierre@0 824
pierre@1 825 // add scale to y
pierre@1 826 if ($y_map) {
pierre@1 827 $graph_y_width = $graph_height / $graph_y;
pierre@1 828 for ($i = 1; $i <= $graph_y; $i++) {
pierre@1 829 $text = number_format($i * $y_map);
pierre@1 830 $len = strlen($text);
pierre@1 831 $x_offset = $graph_width + 14;
pierre@1 832 $y_pos = $graph_height - $i * $graph_y_width;
pierre@1 833 //imagestring($graph, 1, $x_offset, $graph_y_offset + $y_pos - 3, $text, $color['black']);
pierre@1 834 imagestring($graph, 2, $x_offset, $graph_y_offset + $y_pos - 7, $text, $color['black']);
pierre@0 835 }
pierre@1 836 }
pierre@1 837 else {
pierre@1 838 $graph_y_width = 0;
pierre@0 839 }
pierre@0 840
pierre@1 841 // add scale to x
pierre@1 842 $graph_x = _ad_report_select_x($number, 8, 0);
pierre@1 843 $offset = $elapse / $graph_x;
pierre@1 844 $graph_x_width = $graph_width / $graph_x;
pierre@1 845 $x_offset = $graph_x_width / 2;
pierre@1 846 for ($i = 1; $i <= $graph_x; $i++) {
pierre@1 847 $text = date('M d, Y H', $start + ($offset * $i) - $offset / 2);
pierre@1 848 $len = strlen($text);
pierre@1 849 $x_pos = $graph_x_offset - $x_offset + $i * $graph_x_width - 7;
pierre@1 850 $y_pos = $graph_height + $graph_y_offset + ($len * 6) + 3;
pierre@1 851 imagestringup($graph, 2, $x_pos, $y_pos, $text, $color['black']);
pierre@1 852 //$x_pos = $graph_x_offset - $x_offset + $i * $graph_x_width - 4;
pierre@1 853 //$y_pos = $graph_height + $graph_y_offset + ($len * 5) + 3;
pierre@1 854 //imagestringup($graph, 1, $x_pos, $y_pos, $text, $color['black']);
pierre@1 855 }
pierre@1 856
pierre@1 857 // draw a grid
pierre@1 858 $style = array($color['grey'], IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT);
pierre@1 859 imagesetstyle($graph, $style);
pierre@1 860 for ($i = 1; $i <= $graph_x; $i++) {
pierre@1 861 imageline($graph, $graph_x_offset + $i * $graph_x_width - $graph_x_width / 2, $graph_y_offset, $graph_x_offset + $i * $graph_x_width - $graph_x_width / 2, $graph_y_offset + $graph_height - 1, IMG_COLOR_STYLED);
pierre@1 862 }
pierre@1 863 for ($i = 1; $i < $graph_y; $i++) {
pierre@1 864 imageline($graph, $graph_x_offset, $graph_y_offset + $i * $graph_y_width, $graph_x_offset + $graph_width, $graph_y_offset + $i * $graph_y_width, IMG_COLOR_STYLED);
pierre@1 865 }
pierre@1 866 // left, right, top, and bottom borders, respectively
pierre@1 867 imageline($graph, $graph_x_offset, $graph_y_offset, $graph_x_offset, $graph_y_offset + $graph_height, $color['grey']);
pierre@1 868 imageline($graph, $graph_x_offset + $graph_width - 1, $graph_y_offset, $graph_x_offset + $graph_width - 1, $graph_y_offset + $graph_height, $color['grey']);
pierre@1 869 imageline($graph, $graph_x_offset, $graph_y_offset, $graph_x_offset + $graph_width - 1, $graph_y_offset, $color['grey']);
pierre@1 870 imageline($graph, $graph_x_offset, $graph_y_offset + $graph_height, $graph_x_offset + $graph_width - 1, $graph_y_offset + $graph_height, $color['grey']);
pierre@1 871
pierre@1 872 // display the graph
pierre@1 873 imagepng($graph);
pierre@1 874 imagedestroy($graph);
pierre@1 875 }
pierre@1 876
pierre@1 877 /**
pierre@1 878 * Figure out how many x columns to display.
pierre@1 879 * TODO: Find a better algorithm than this slop.
pierre@1 880 */
pierre@1 881 function _ad_report_select_x($number, $divisor, $diff) {
pierre@1 882 if ($divisor < 2) {
pierre@1 883 return $number;
pierre@1 884 }
pierre@1 885 $divisor = $divisor + $diff;
pierre@1 886 if ($divisor == 0) {
pierre@1 887 $divisor = $divisor + $diff;
pierre@1 888 }
pierre@1 889 $result = (int)($number / $divisor);
pierre@1 890 if ($result < 8) {
pierre@1 891 $diff -= 1;
pierre@1 892 if ($diff) {
pierre@1 893 return _ad_report_select_x($number, $divisor, $diff);
pierre@0 894 }
pierre@0 895 }
pierre@1 896 else if ($result > 12) {
pierre@1 897 $diff += 1;
pierre@1 898 if ($diff) {
pierre@1 899 return _ad_report_select_x($number, $divisor, $diff);
pierre@1 900 }
pierre@1 901 }
pierre@1 902 return $result;
pierre@0 903 }