Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
search_log_report_edit_fns.inc
1 <?php
17 require_once SQ_CORE_PACKAGE_PATH.'/log_report/log_report_edit_fns.inc';
18 require_once SQ_FUDGE_PATH.'/general/file_system.inc';
19 require_once SQ_LIB_PATH.'/html_form/html_form.inc';
20 
33 {
34 
35 
46  function generateReport(&$job, &$step_data, $prefix)
47  {
48  $job_vars =& $job->_running_vars;
49 
50  // set metadata that doesn't require the line data
51  $meta['report'] =& $job_vars['report'];
52 
53  // check job_vars, and set defaults if required
54  if (isset($job_vars['iterator'])) {
55  $iterator =& $job_vars['iterator'];
56  } else {
57  $iterator =& $GLOBALS['SQ_SYSTEM']->log_manager->getLogIterator($meta['report']->logname, 1);
58  }
59 
60  // default to updating the terms
61  if (!isset($job_vars['mode'])) {
62  $job_vars['mode'] = 'terms';
63  }
64 
65  // determine the mode
66  // terms - read the terms from disk, update them with data from the log, and write it back out
67  if ($job_vars['mode'] == 'terms') {
68 
69  // get the next line and format the data retrieved
70  // if a carryover from last step exists, use that instead.
71  if (!empty($job_vars['carryover'])) {
72  $line = $job_vars['carryover'];
73  unset($job_vars['carryover']);
74  } else {
75  $line = $iterator->getNextEntry();
76  }
77 
78  if (!empty($line)) {
79  $data = array_get_index($line, 'data', Array());
80 
81  // prepare the metadata to hand out to the term-processing functions
82  $meta['userid'] = $line['userid'];
83  $meta['user'] = $line['user'];
84  $meta['date'] = $line['date'];
85  $meta['level'] = $line['level'];
86  $meta['search_assetid'] = array_get_index($data, 'assetid', 0);
87 
88  // Get previous date from last rotated log
89  if (!isset($job_vars['previous_date'])) {
90  $job_vars['previous_date'] = $this->_getPreviousDate($meta['report']);
91  }
92 
93  // determine whether we need to rollover to the next day/week/month before processing the line
94  $rollover = FALSE;
95  if (!is_null(array_get_index($job_vars, 'previous_date', NULL))) {
96  switch ($job_vars['report']->attr('rollover_period')) {
97  case 'daily':
98  // if the date of current line is greater than the date of the previous line, rollover
99  if (date('Ymd', $meta['date']) > date('Ymd', $job_vars['previous_date'])) {
100  $rollover = TRUE;
101  }
102  break;
103 
104  case 'monthly':
105  // if the month of the current line is greater than the month of previous line, rollover
106  if (date('Ym', $meta['date']) > date('Ym', $job_vars['previous_date'])) {
107  $rollover = TRUE;
108  }
109  break;
110 
111  case 'weekly':
112  default:
113  // get unix time for the start of next week, do as above
114  $next_week_start_time = $job_vars['previous_date'] + ((7 - date('w', $job_vars['previous_date'])) * 86400);
115  if (date('Ymd', $meta['date']) >= date('Ymd', $next_week_start_time)) {
116  $rollover = TRUE;
117  }
118  break;
119  }
120 
121  if ($rollover) {
122  // rollover needs to be performed, switch mode over to calculating the indices
123  $job_vars['carryover'] = $line;
124  $job_vars['mode'] = 'indices';
125  }
126  }//end if
127 
128  // set the previous date in the running vars to handle rollover
129  $job_vars['previous_date'] = $line['date'];
130 
131  if (!$rollover) {
132  // get the terms, strip the tags, then process them
133  $terms = array_get_index($data, 'terms', Array());
134  foreach ($terms as $term => $result) {
135  while ($term != strip_tags($term)) {
136  $term = strip_tags($term);
137  }
138  $this->_updateTerm($term, $result, $meta);
139  }
140  }
141  }//end if (!empty($line))
142 
143  // update the progress bar
144  $progress =& $iterator->getCurrentProgress();
145  $total = array_get_index($progress, 'total', 0);
146  $current = array_get_index($progress, 'current', $total);
147  if ($total == 0) {
148  $step_data['percent_done'] = 50;
149  } else {
150  $step_data['percent_done'] = (int) ($current * 100 / $total) / 2;
151  }
152 
153  // finished with updating the terms, switch to calculating the indices
154  if ($current >= $total) $job_vars['mode'] = 'indices';
155 
156  }//end if ($job_vars['mode'] == 'terms')
157 
158 
159  // indices - loads all the terms stored in the term cache, ranks them
160  // and strips out all but the top $report->attr('row_count') terms
161  if ($job_vars['mode'] == 'indices') {
162 
163  $term_dir_path = $meta['report']->data_path.'/terms';
164 
165  // if the term_index hasn't been constructed, generate it from disk
166  if (!isset($job_vars['term_index'])) {
167  // construct term array
168  $ids = glob($term_dir_path.'/*');
169  foreach ($ids as $id) {
170  $id = str_replace($term_dir_path.'/', '', $id);
171  // NOTE: assumes a depth of two (terms/assetid/12/34/foobar.term)
172  $terms = glob($term_dir_path.'/'.$id.'/*/*/*');
173  foreach ($terms as $term) {
174  $term = basename($term, '.term');
175  // by keeping only the term and its associated asset ids in memory, we keep the overhead down
176  $job_vars['term_index'][$term][] = $id;
177  }
178  }
179  }
180 
181  // set up the counters for the progress bar
182  if (!isset($job_vars['term_index_total'])) {
183  $job_vars['term_index_total'] = count($job_vars['term_index']);
184  }
185 
186  if (!isset($job_vars['term_index_count'])) {
187  $job_vars['term_index_count'] = 0;
188  } else {
189  if ($job_vars['term_index_count'] >= $job_vars['term_index_total']) {
190  $job_vars['term_index_count'] = $job_vars['term_index_total'];
191  } else {
192  $job_vars['term_index_count']++;
193  }
194  }
195 
196  // process the next term on the list
197  if (!empty($job_vars['term_index'])) {
198 
199  $term = end(array_keys($job_vars['term_index']));
200  $ids = array_pop($job_vars['term_index']);
201 
202  foreach ($ids as $id) {
203  $data = $this->_readTerm($term, $term_dir_path.'/'.$id.'/'.$this->_getTermHash($term));
204  if ($data) {
205  foreach ($data as $date => $results) {
206  // make sure the indices are set
207  if (!isset($job_vars['indices'][$id][$date]['index_popular'])) {
208  $job_vars['indices'][$id][$date]['index_popular'] = Array();
209  }
210  if (!isset($job_vars['indices'][$id][$date]['index_failed'])) {
211  $job_vars['indices'][$id][$date]['index_failed'] = Array();
212  }
213  $row_count = $meta['report']->attr('row_count');
214 
215  // update the indices
216  $total_value = array_get_index($results, 'total_searches', 0);
217  $zero_value = array_get_index($results, 'zero_results', 0);
218  if ($zero_value) {
219  $this->_updateIndex($job_vars['indices'][$id][$date]['index_failed'], $term, $zero_value, $row_count);
220  } else {
221  $this->_updateIndex($job_vars['indices'][$id][$date]['index_popular'], $term, $total_value, $row_count);
222  }
223  }
224  }
225  }
226  // remove the term from the list
227  unset($job_vars['term_index'][$term]);
228  } else {
229  // we're finished, move onto the next step
230  unset($job_vars['term_index']);
231  $job_vars['mode'] = 'generate';
232  }
233 
234  // set the progress bar
235  $step_data['percent_done'] = (($job_vars['term_index_count'] * 100 / $job_vars['term_index_total']) / 2) + 50;
236 
237  }//end if ($job_vars['mode'] == 'indices')
238 
239 
240  // generate - generate the report cache
241  if ($job_vars['mode'] == 'generate') {
242 
243  $result = $this->_generateReportCache($meta, $job_vars['indices']);
244 
245  $progress =& $iterator->getCurrentProgress();
246  $total = array_get_index($progress, 'total', 0);
247  $current = array_get_index($progress, 'current', $total);
248 
249  if ($current >= $total) {
250  // we're done
251  $step_data['percent_done'] = 100;
252  $step_data['complete'] = TRUE;
253  } else {
254  // the only time we'd be here is if we haven't yet finished reading
255  // the file and need to rollover
256  $job_vars['mode'] = 'rollover';
257  }
258  }//end if($job_vars['mode'] == 'generate')
259 
260 
261  // rollover - archive the current report so that we can start writing another one for the new day/week/month
262  if ($job_vars['mode'] == 'rollover') {
263  $this->_performRollover($meta['report'], $job_vars['previous_date']);
264  $job_vars['indices'] = Array();
265 
266  $progress =& $iterator->getCurrentProgress();
267  $total = array_get_index($progress, 'total', 0);
268  $current = array_get_index($progress, 'current', $total);
269  if ($current >= $total && !isset($job_vars['carryover'])) {
270  $step_data['percent_done'] = 100;
271  $step_data['complete'] = TRUE;
272  } else {
273  // More terms to process, switch back to 'terms'
274  $job_vars['mode'] = 'terms';
275  }
276  }//end if($job_vars['mode'] == 'rollover')
277 
278 
279  $job_vars['iterator'] =& $iterator;
280  $job_vars['complete'] = $step_data['complete'];
281 
282  return TRUE;
283 
284  }//end generateReport()
285 
286 
310  function _updateTerm($term, $result, &$meta)
311  {
312  if (empty($term) || empty($meta)) {
313  return FALSE;
314  }
315 
316  $search_assetid = array_get_index($meta, 'search_assetid', '0');
317  $meta_date = date('Y-m-d H', $meta['date']);
318 
319  // include the current assetid as well the grand total
320  $assetids = Array(
321  $search_assetid,
322  'total',
323  );
324 
325  // same for dates
326  $dates = Array(
327  date('Y-m-d H', $meta['date']),
328  'total',
329  );
330 
331  foreach ($assetids as $id) {
332  $term_dir_path = $meta['report']->data_path.'/terms'.('/'.$id).$this->_getTermHash($term);
333 
334  // read, update and write for the specific assetid or total
335  $data_collection[$id] = $this->_readTerm($term, $term_dir_path);
336  foreach ($dates as $date) {
337  // prepare data, and update the value for each term
338  if (!isset($data_collection[$id][$date])) {
339  $data_collection[$id][$date] = Array();
340  }
341  $this->_updateTermValues($term, $result, $data_collection[$id][$date]);
342  }
343  // refresh the term cache with the new data
344  $this->_writeTerm($term, $term_dir_path, $data_collection[$id]);
345  }
346 
347  return TRUE;
348 
349  }//end _updateTerm()
350 
351 
362  function _updateTermValues($term, $result, &$data)
363  {
364  if (empty($term)) return FALSE;
365 
366  // update the stats, or create new ones if not already present
367  $total_searches = array_get_index($data, 'total_searches', 0) + 1;
368 
369  if ($result == 0) {
370  $zero_results = array_get_index($data, 'zero_results', 0) + 1;
371  } else {
372  $zero_results = array_get_index($data, 'zero_results', 0);
373  }
374 
375  if ($total_searches > 0) {
376  $average_result = ((array_get_index($data, 'average_result', 0) * ($total_searches - 1)) + $result) / $total_searches;
377  } else {
378  $average_result = $result;
379  }
380 
381  if (is_null(array_get_index($data, 'max_result', NULL))) {
382  $max_result = $result;
383  } else {
384  $max_result = ($result > $data['max_result'] ? $result : $data['max_result']);
385  }
386 
387  if (is_null(array_get_index($data, 'min_result', NULL))) {
388  $min_result = $result;
389  } else {
390  $min_result = ($result < $data['min_result'] ? $result : $data['min_result']);
391  }
392 
393  $data = Array(
394  'total_searches' => $total_searches,
395  'zero_results' => $zero_results,
396  'average_result' => round($average_result, 2),
397  'max_result' => $max_result,
398  'min_result' => $min_result,
399  );
400 
401  return TRUE;
402 
403  }//end _updateTermValues()
404 
405 
415  function &_readTerm($term, $term_dir_path)
416  {
417  if (empty($term) || empty($term_dir_path)) {
418  return Array();
419  }
420 
421  $term_path = $term_dir_path.'/'.$term.'.term';
422 
423  // dump the contents of the file into an array, unserialising as we go
424  $data = Array();
425  if (file_exists($term_path)) {
426  $fp = fopen($term_path, 'r');
427  if ($fp) {
428  while (!feof($fp)) {
429  $data = unserialize(fgets($fp));
430  }
431  fclose($fp);
432  }
433  }
434 
435  return $data;
436 
437  }//end _readTerm()
438 
439 
450  function _writeTerm($term, $term_dir_path, &$data)
451  {
452  if (empty($term) || empty($term_dir_path) || empty($data)) {
453  return FALSE;
454  }
455 
456  // create the directories as we need them
457  if (!is_dir($term_dir_path) && !create_directory($term_dir_path)) {
458  return FALSE;
459  }
460  // bug fix 3587 converting slashes in the search term (if any) to underscore
461  // so that file system doesnt take it as directory structure
462  $term = str_replace('/' , '_' , $term);
463  $term = urlencode($term);
464  $term = substr_replace($term, '', 250, strlen($term));
465 
466  $term_path = $term_dir_path.'/'.$term.'.term';
467 
468  // dump the into the file, serialising as we go
469  $fp = fopen($term_path, 'w');
470  if ($fp) {
471  fwrite($fp, serialize($data));
472  fclose($fp);
473  return TRUE;
474  }
475 
476  return FALSE;
477 
478  }//end _writeTerm()
479 
480 
493  function _updateIndex(&$array, $index, $value, $limit)
494  {
495  if (empty($index) || $value <= 0 || $limit < 1) {
496  return FALSE;
497  }
498 
499  // if the element already exists, or the array isn't big enough, add the element
500  if (isset($array[$index]) || count($array) < $limit) {
501  $array[$index] = $value;
502  } else {
503  // assume at least one element in the array
504  // calculate threshold
505  $threshold = end($array);
506  reset($array);
507 
508  // if the value is above what we need, pop off the last and add the new element.
509  if ($value > $threshold) {
510  array_pop($array);
511  $array[$index] = $value;
512  }
513  }
514 
515  // re-sort
516  arsort($array);
517  return TRUE;
518 
519  }//end _updateIndex()
520 
521 
531  function _generateReportCache(&$meta, &$indices)
532  {
533  if (empty($meta)) return FALSE;
534 
535  $logname = $meta['report']->logname;
536  $data_path = $meta['report']->data_path;
537  $term_dir_path = $data_path.'/terms';
538  $generated_dir_path = $data_path.'/generated';
539 
540  if (empty($data_path)) return FALSE;
541 
542  // get all the assetids in the terms
543  $assetids = $this->_getSearchAssetids($term_dir_path);
544  if (empty($assetids)) {
545  $assetids = Array(
546  'total',
547  );
548  }
549 
550 
551  // loop through the asset ids, using the indices to prepare the output containing the top results
552  foreach ($assetids as $id) {
553 
554  if (!isset($indices[$id]['total']['index_popular'])) {
555  $indices[$id]['total']['index_popular'] = Array();
556  }
557  if (!isset($indices[$id]['total']['index_failed'])) {
558  $indices[$id]['total']['index_failed'] = Array();
559  }
560 
561  $index_popular = $indices[$id]['total']['index_popular'];
562  $index_failed = $indices[$id]['total']['index_failed'];
563 
564  // popular searches
565  $output_index_popular = '';
566  foreach ($index_popular as $term => $result) {
567  $data_collection = $this->_readTerm($term, $term_dir_path.('/'.$id).$this->_getTermHash($term));
568  if (isset($data_collection['total'])) {
569  $data = $data_collection['total'];
570  $output_index_popular .= '
571  <tr>
572  <td class="sq-backend-table-cell">'.urldecode($term).'</td>
573  <td class="sq-backend-table-cell" style="width: 25px;">'.array_get_index($data, 'total_searches', 0).'</td>
574  <td class="sq-backend-table-cell" style="width: 25px;">'.array_get_index($data, 'average_result', 0).'</td>
575  <td class="sq-backend-table-cell" style="width: 25px;">'.array_get_index($data, 'min_result', 0).'</td>
576  <td class="sq-backend-table-cell" style="width: 25px;">'.array_get_index($data, 'max_result', 0).'</td>
577  </tr>
578  ';
579  }
580  }
581 
582  // failed searches
583  $output_index_failed = '';
584  foreach ($index_failed as $term => $result) {
585  $output_index_failed .= '
586  <tr>
587  <td class="sq-backend-table-cell">'.urldecode($term).'</td>
588  <td class="sq-backend-table-cell" style="width: 25px;">'.$result.'</td>
589  </tr>
590  ';
591  }
592 
593 
594  // put it all together
595  if ($id == 'total') {
596  $output_heading = '<b>'.translate('sch_log_report_all_search_pages').'</b>';
597  } else {
598  if ($GLOBALS['SQ_SYSTEM']->am->assetExists($id)) {
599  $asset = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo(Array($id), Array(), FALSE, 'name');
600  $asset = $asset[$id];
601  }
602 
603  if (!$asset) $asset = 'Asset';
604 
605  $output_heading = '<b>'.$asset.' (Id: #'.$id.')</b>';
606  }
607  $output[$id] = $output_heading.'
608 
609  <table class="sq-backend-table">
610  <tr>
611  <td class="sq-backend-table-header" style="width: 200px">'.translate('sch_log_report_popular_search_terms').'</td>
612  <td class="sq-backend-table-header">'.translate('sch_log_report_total_searches').'</td>
613  <td class="sq-backend-table-header">'.translate('sch_log_report_average_results').'</td>
614  <td class="sq-backend-table-header">'.translate('lowest').'</td>
615  <td class="sq-backend-table-header">'.translate('highest').'</td>
616  </tr>
617  '.(!empty($output_index_popular) ? $output_index_popular : '<td class="sq-backend-table-cell" colspan="5">'.translate('sch_log_report_no_successful_searches').'</td>').'
618  </table>
619 
620  <table class="sq-backend-table">
621  <tr>
622  <td class="sq-backend-table-header" style="width: 200px">'.translate('sch_log_report_failed_search_terms').'</td>
623  <td class="sq-backend-table-header">'.translate('sch_log_report_total_failed_searches').'</td>
624  </tr>
625  '.(!empty($output_index_failed) ? $output_index_failed : '<td class="sq-backend-table-cell" colspan="2">'.translate('sch_log_report_no_failed_searches').'</td>').'
626  </table>
627  <br />
628  ';
629 
630  // make sure we have somewhere to write to
631  if (!file_exists($generated_dir_path)) {
632  if (!create_directory($generated_dir_path)) {
633  return FALSE;
634  }
635  }
636 
637  }//end foreach
638 
639  // make sure we always print the total first
640  $report_output = '
641  '.$output['total'].'
642  ';
643 
644  unset($output['total']);
645  $report_output .= implode("\n", $output);
646 
647  $result = string_to_file($report_output, $generated_dir_path.'/'.$logname.'.html');
648  if ($result) {
649  $meta['report']->setAttrValue('generated_date', date('F j, Y, g:i a'));
650  $meta['report']->setAttrValue('generated_user', $GLOBALS['SQ_SYSTEM']->user->name.' (#'.$GLOBALS['SQ_SYSTEM']->user->id.')');
651 
652  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
653  $meta['report']->saveAttributes();
654  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
655  }
656  return $result;
657 
658  }//end _generateReportCache()
659 
660 
670  function _performRollover(&$report, $date)
671  {
672  if (empty($report) || empty($date)) {
673  return FALSE;
674  }
675 
676  $formatted_date = date('Y-m-d', $date);
677  $logname = $report->logname;
678  $data_path = $report->data_path;
679  $term_dir_path = $data_path.'/terms';
680  $generated_dir_path = $data_path.'/generated';
681 
682  // remove term cache
683  if (!delete_directory($term_dir_path)) {
684  trigger_localised_error('SCH0025', E_USER_WARNING);
685  return FALSE;
686  }
687 
688  // move report to archives
689  $from = $generated_dir_path.'/'.$logname.'.html';
690  $to = $generated_dir_path.'/'.$logname.'.'.$formatted_date.'.html';
691  if (!rename($from, $to)) {
692  trigger_localised_error('SCH0024', E_USER_WARNING);
693  return FALSE;
694  }
695 
696  // update settings
697  $generated_reports = $report->attr('generated_reports');
698  $generated_reports[$logname.'.'.$formatted_date] = $formatted_date.' ('.ucwords($report->attr('rollover_period')).')';
699  arsort($generated_reports);
700 
701  $report->setAttrValue('generated_reports', $generated_reports);
702  $GLOBALS['SQ_SYSTEM']->setRunLevel(SQ_RUN_LEVEL_FORCED);
703  $report->saveAttributes();
704  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
705 
706  return TRUE;
707 
708  }//end _performRollover()
709 
710 
719  function _getSearchAssetids($term_dir_path)
720  {
721  if (empty($term_dir_path)) return FALSE;
722 
723  $dirs = Array();
724  if (is_dir($term_dir_path)) {
725  if ($dir_handle = opendir($term_dir_path)) {
726  while (($file = readdir($dir_handle)) !== FALSE) {
727  if ($file != '.' && $file != '..' && is_dir($term_dir_path.'/'.$file)) {
728  $dirs[] = $file;
729  }
730  }
731  }
732  }
733  return $dirs;
734 
735  }//end _getSearchAssetids()
736 
737 
747  function _getTermHash($term)
748  {
749  if (trim($term) == '') return FALSE;
750  $term = urldecode($term);
751 
752  // how many directory levels to hash to (eg. 2 = '/12/34')
753  $depth = 2;
754  $hash_num = Array();
755  for ($ii = 0; $ii < $depth; $ii++) {
756  // if the word isn't long enough, pad any missing characters with 0
757  if (strlen($term) > $ii) {
758  $hash_num[] .= ord($term[$ii]);
759  } else {
760  $hash_num[] .= 0;
761  }
762  }
763 
764  $hash = implode('/', $hash_num);
765 
766  return '/'.$hash;
767 
768  }//end _getTermHash()
769 
770 
781  function paintReport(&$asset, &$o, $prefix)
782  {
783  $generated_reports = $asset->attr('generated_reports');
784 
785  // there will always be a current report, so add it here
786  $generated_reports[$asset->logname] = 'Current';
787  arsort($generated_reports);
788 
789  if (isset($_REQUEST[$prefix]['generated_reports'])) {
790  if (isset($generated_reports[$_REQUEST[$prefix]['generated_reports']])) {
791  $report_path = $asset->data_path.'/generated/'.$_REQUEST[$prefix]['generated_reports'].'.html';
792  $selected = $_REQUEST[$prefix]['generated_reports'];
793  } else {
794  $report_path = $asset->data_path.'/generated/'.$asset->logname.'.html';
795  $selected = $asset->logname;
796  }
797  } else {
798  $report_path = $asset->data_path.'/generated/'.$asset->logname.'.html';
799  $selected = $asset->logname;
800  }
801 
802  if (!is_file($report_path)) {
803  echo translate('sch_log_report_generate_at_next_rotation');
804  return;
805  }
806 
807  echo '<h3>'.translate('sch_log_report_search_report').'</h3>';
808 
809  // print the report selector
810  combo_box($prefix.'[generated_reports]', $generated_reports, FALSE, $selected);
811  echo '&nbsp;';
812  submit_button($prefix.'[select_report]', translate('select_report'));
813  echo '<br /><br />';
814 
815  include_once($report_path);
816 
817  }//end paintReport()
818 
819 
830  function paintRegenerate(&$asset, &$o, $prefix)
831  {
832  $generated_user = $asset->attr('generated_user');
833  if (empty($generated_user)) {
834  echo translate('report_not_generated').'<br />';
835  } else {
836  echo '<p>';
837  echo ' <b>'.translate('report_generated').'</b><br />';
838  echo ' '.translate('date').': '.$asset->attr('generated_date');
839  echo ' <br />';
840  echo ' '.translate('user').': '.$asset->attr('generated_user');
841  echo '</p>';
842  }
843 
844  return FALSE;
845 
846  }//end paintRegenerate()
847 
848 
857  function _getPreviousDate($report)
858  {
859  $generated_reports = $report->attr('generated_reports');
860  $latest_report = current(array_keys($generated_reports));
861 
862  $last_date = NULL;
863  if ($latest_report) {
864  $last_date = strtotime(substr($latest_report, 7));
865  }
866 
867  return $last_date;
868 
869  }//end _getPreviousDate()
870 
871 
872 }//end class
873 
874 ?>