Upgraded phpmyadmin to 4.0.4 (All Languages) - No modifications yet
[openemr.git] / phpmyadmin / libraries / Advisor.class.php
blob91c8bf2bebbb8c7db037864fdb956e9a9c5e96a3
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * A simple rules engine, that parses and executes the rules in advisory_rules.txt.
5 * Adjusted to phpMyAdmin.
7 * @package PhpMyAdmin
8 */
9 if (! defined('PHPMYADMIN')) {
10 exit;
13 /**
14 * Advisor class
16 * @package PhpMyAdmin
18 class Advisor
20 var $variables;
21 var $parseResult;
22 var $runResult;
24 /**
25 * Parses and executes advisor rules
27 * @return array with run and parse results
29 function run()
31 // HowTo: A simple Advisory system in 3 easy steps.
33 // Step 1: Get some variables to evaluate on
34 $this->variables = array_merge(
35 PMA_DBI_fetch_result('SHOW GLOBAL STATUS', 0, 1),
36 PMA_DBI_fetch_result('SHOW GLOBAL VARIABLES', 0, 1)
38 if (PMA_DRIZZLE) {
39 $this->variables = array_merge(
40 $this->variables,
41 PMA_DBI_fetch_result(
42 "SELECT concat('Com_', variable_name), variable_value
43 FROM data_dictionary.GLOBAL_STATEMENTS", 0, 1
47 // Add total memory to variables as well
48 include_once 'libraries/sysinfo.lib.php';
49 $sysinfo = PMA_getSysInfo();
50 $memory = $sysinfo->memory();
51 $this->variables['system_memory'] = $memory['MemTotal'];
53 // Step 2: Read and parse the list of rules
54 $this->parseResult = $this->parseRulesFile();
55 // Step 3: Feed the variables to the rules and let them fire. Sets
56 // $runResult
57 $this->runRules();
59 return array(
60 'parse' => array('errors' => $this->parseResult['errors']),
61 'run' => $this->runResult
65 /**
66 * Stores current error in run results.
68 * @param string $description description of an error.
69 * @param object $exception exception raised
71 * @return void
73 function storeError($description, $exception)
75 $this->runResult['errors'][] = $description
76 . ' '
77 . sprintf(__('PHP threw following error: %s'), $exception->getMessage());
80 /**
81 * Executes advisor rules
83 * @return void
85 function runRules()
87 $this->runResult = array(
88 'fired' => array(),
89 'notfired' => array(),
90 'unchecked'=> array(),
91 'errors' => array()
94 foreach ($this->parseResult['rules'] as $rule) {
95 $this->variables['value'] = 0;
96 $precond = true;
98 if (isset($rule['precondition'])) {
99 try {
100 $precond = $this->ruleExprEvaluate($rule['precondition']);
101 } catch (Exception $e) {
102 $this->storeError(
103 sprintf(
104 __('Failed evaluating precondition for rule \'%s\''),
105 $rule['name']
109 continue;
113 if (! $precond) {
114 $this->addRule('unchecked', $rule);
115 } else {
116 try {
117 $value = $this->ruleExprEvaluate($rule['formula']);
118 } catch(Exception $e) {
119 $this->storeError(
120 sprintf(
121 __('Failed calculating value for rule \'%s\''),
122 $rule['name']
126 continue;
129 $this->variables['value'] = $value;
131 try {
132 if ($this->ruleExprEvaluate($rule['test'])) {
133 $this->addRule('fired', $rule);
134 } else {
135 $this->addRule('notfired', $rule);
137 } catch(Exception $e) {
138 $this->storeError(
139 sprintf(
140 __('Failed running test for rule \'%s\''),
141 $rule['name']
149 return true;
153 * Escapes percent string to be used in format string.
155 * @param string $str string to escape
157 * @return string
159 static function escapePercent($str)
161 return preg_replace('/%( |,|\.|$|\(|\)|<|>)/', '%%\1', $str);
165 * Wrapper function for translating.
167 * @param string $str the string
168 * @param mixed $param the parameters
170 * @return string
172 function translate($str, $param = null)
174 if (is_null($param)) {
175 return sprintf(_gettext(Advisor::escapePercent($str)));
176 } else {
177 $printf = 'sprintf("' . _gettext(Advisor::escapePercent($str)) . '",';
178 return $this->ruleExprEvaluate(
179 $printf . $param . ')',
180 strlen($printf)
186 * Splits justification to text and formula.
188 * @param string $rule the rule
190 * @return array
192 static function splitJustification($rule)
194 $jst = preg_split('/\s*\|\s*/', $rule['justification'], 2);
195 if (count($jst) > 1) {
196 return array($jst[0], $jst[1]);
198 return array($rule['justification']);
202 * Adds a rule to the result list
204 * @param string $type type of rule
205 * @param array $rule rule itslef
207 * @return void
209 function addRule($type, $rule)
211 switch($type) {
212 case 'notfired':
213 case 'fired':
214 $jst = Advisor::splitJustification($rule);
215 if (count($jst) > 1) {
216 try {
217 /* Translate */
218 $str = $this->translate($jst[0], $jst[1]);
219 } catch (Exception $e) {
220 $this->storeError(
221 sprintf(
222 __('Failed formatting string for rule \'%s\'.'),
223 $rule['name']
227 return;
230 $rule['justification'] = $str;
231 } else {
232 $rule['justification'] = $this->translate($rule['justification']);
234 $rule['id'] = $rule['name'];
235 $rule['name'] = $this->translate($rule['name']);
236 $rule['issue'] = $this->translate($rule['issue']);
238 // Replaces {server_variable} with 'server_variable'
239 // linking to server_variables.php
240 $rule['recommendation'] = preg_replace(
241 '/\{([a-z_0-9]+)\}/Ui',
242 '<a href="server_variables.php?' . PMA_generate_common_url() . '&filter=\1">\1</a>',
243 $this->translate($rule['recommendation'])
246 // Replaces external Links with PMA_linkURL() generated links
247 $rule['recommendation'] = preg_replace_callback(
248 '#href=("|\')(https?://[^\1]+)\1#i',
249 array($this, '_replaceLinkURL'),
250 $rule['recommendation']
252 break;
255 $this->runResult[$type][] = $rule;
259 * Callback for wrapping links with PMA_linkURL
261 * @param array $matches List of matched elements form preg_replace_callback
263 * @return Replacement value
265 private function _replaceLinkURL($matches)
267 return 'href="' . PMA_linkURL($matches[2]) . '"';
271 * Callback for evaluating fired() condition.
273 * @param array $matches List of matched elements form preg_replace_callback
275 * @return Replacement value
277 private function _ruleExprEvaluateFired($matches)
279 // No list of fired rules
280 if (!isset($this->runResult['fired'])) {
281 return '0';
284 // Did matching rule fire?
285 foreach ($this->runResult['fired'] as $rule) {
286 if ($rule['id'] == $matches[2]) {
287 return '1';
291 return '0';
295 * Callback for evaluating variables in expression.
297 * @param array $matches List of matched elements form preg_replace_callback
299 * @return Replacement value
301 private function _ruleExprEvaluateVariable($matches)
303 return isset($this->variables[$matches[1]])
304 ? (is_numeric($this->variables[$matches[1]])
305 ? $this->variables[$matches[1]]
306 : '"'.$this->variables[$matches[1]].'"')
307 : $matches[1];
311 * Runs a code expression, replacing variable names with their respective
312 * values
314 * @param string $expr expression to evaluate
315 * @param int $ignoreUntil if > 0, it doesn't replace any variables until
316 * that string position, but still evaluates the
317 * whole expr
319 * @return result of evaluated expression
321 function ruleExprEvaluate($expr, $ignoreUntil = 0)
323 if ($ignoreUntil > 0) {
324 $exprIgnore = substr($expr, 0, $ignoreUntil);
325 $expr = substr($expr, $ignoreUntil);
327 // Evaluate fired() conditions
328 $expr = preg_replace_callback(
329 '/fired\s*\(\s*(\'|")(.*)\1\s*\)/Ui',
330 array($this, '_ruleExprEvaluateFired'),
331 $expr
333 // Evaluate variables
334 $expr = preg_replace_callback(
335 '/\b(\w+)\b/',
336 array($this, '_ruleExprEvaluateVariable'),
337 $expr
339 if ($ignoreUntil > 0) {
340 $expr = $exprIgnore . $expr;
342 $value = 0;
343 $err = 0;
345 // Actually evaluate the code
346 ob_start();
347 eval('$value = ' . $expr . ';');
348 $err = ob_get_contents();
349 ob_end_clean();
351 // Error handling
352 if ($err) {
353 throw new Exception(
354 strip_tags($err) . '<br />Executed code: $value = ' . htmlspecialchars($expr) . ';'
357 return $value;
361 * Reads the rule file into an array, throwing errors messages on syntax
362 * errors.
364 * @return array with parsed data
366 static function parseRulesFile()
368 $file = file('libraries/advisory_rules.txt', FILE_IGNORE_NEW_LINES);
369 $errors = array();
370 $rules = array();
371 $lines = array();
372 $ruleSyntax = array(
373 'name', 'formula', 'test', 'issue', 'recommendation', 'justification'
375 $numRules = count($ruleSyntax);
376 $numLines = count($file);
377 $ruleNo = -1;
378 $ruleLine = -1;
380 for ($i = 0; $i < $numLines; $i++) {
381 $line = $file[$i];
382 if ($line == "" || $line[0] == '#') {
383 continue;
386 // Reading new rule
387 if (substr($line, 0, 4) == 'rule') {
388 if ($ruleLine > 0) {
389 $errors[] = sprintf(
390 __('Invalid rule declaration on line %1$s, expected line %2$s of previous rule'),
391 $i + 1,
392 $ruleSyntax[$ruleLine++]
394 continue;
396 if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/", $line, $match)) {
397 $ruleLine = 1;
398 $ruleNo++;
399 $rules[$ruleNo] = array('name' => $match[1]);
400 $lines[$ruleNo] = array('name' => $i + 1);
401 if (isset($match[3])) {
402 $rules[$ruleNo]['precondition'] = $match[3];
403 $lines[$ruleNo]['precondition'] = $i + 1;
405 } else {
406 $errors[] = sprintf(
407 __('Invalid rule declaration on line %s'),
408 $i + 1
411 continue;
412 } else {
413 if ($ruleLine == -1) {
414 $errors[] = sprintf(
415 __('Unexpected characters on line %s'),
416 $i + 1
421 // Reading rule lines
422 if ($ruleLine > 0) {
423 if (!isset($line[0])) {
424 continue; // Empty lines are ok
426 // Non tabbed lines are not
427 if ($line[0] != "\t") {
428 $errors[] = sprintf(
429 __('Unexpected character on line %1$s. Expected tab, but found "%2$s"'),
430 $i + 1,
431 $line[0]
433 continue;
435 $rules[$ruleNo][$ruleSyntax[$ruleLine]] = chop(substr($line, 1));
436 $lines[$ruleNo][$ruleSyntax[$ruleLine]] = $i + 1;
437 $ruleLine += 1;
440 // Rule complete
441 if ($ruleLine == $numRules) {
442 $ruleLine = -1;
446 return array('rules' => $rules, 'lines' => $lines, 'errors' => $errors);
451 * Formats interval like 10 per hour
453 * @param integer $num number to format
454 * @param intefer $precision required precision
456 * @return formatted string
458 function ADVISOR_bytime($num, $precision)
460 $per = '';
461 if ($num >= 1) { // per second
462 $per = __('per second');
463 } elseif ($num * 60 >= 1) { // per minute
464 $num = $num * 60;
465 $per = __('per minute');
466 } elseif ($num * 60 * 60 >= 1 ) { // per hour
467 $num = $num * 60 * 60;
468 $per = __('per hour');
469 } else {
470 $num = $num * 60 * 60 * 24;
471 $per = __('per day');
474 $num = round($num, $precision);
476 if ($num == 0) {
477 $num = '<' . PMA_Util::pow(10, -$precision);
480 return "$num $per";
484 * Wrapper for PMA_Util::timespanFormat
486 * @param int $seconds the timespan
488 * @return string the formatted value
490 function ADVISOR_timespanFormat($seconds)
492 return PMA_Util::timespanFormat($seconds);
496 * Wrapper around PMA_Util::formatByteDown
498 * @param double $value the value to format
499 * @param int $limes the sensitiveness
500 * @param int $comma the number of decimals to retain
502 * @return array the formatted value and its unit
504 function ADVISOR_formatByteDown($value, $limes = 6, $comma = 0)
506 return PMA_Util::formatByteDown($value, $limes, $comma);