Translated using Weblate (Catalan)
[phpmyadmin.git] / libraries / Advisor.class.php
blobfed6d19a238825ec2e694b0703e22988b76e5739
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 public 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 $GLOBALS['dbi']->fetchResult('SHOW GLOBAL STATUS', 0, 1),
36 $GLOBALS['dbi']->fetchResult('SHOW GLOBAL VARIABLES', 0, 1)
38 if (PMA_DRIZZLE) {
39 $this->variables = array_merge(
40 $this->variables,
41 $GLOBALS['dbi']->fetchResult(
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']
52 = isset($memory['MemTotal']) ? $memory['MemTotal'] : 0;
54 // Step 2: Read and parse the list of rules
55 $this->parseResult = $this->parseRulesFile();
56 // Step 3: Feed the variables to the rules and let them fire. Sets
57 // $runResult
58 $this->runRules();
60 return array(
61 'parse' => array('errors' => $this->parseResult['errors']),
62 'run' => $this->runResult
66 /**
67 * Stores current error in run results.
69 * @param string $description description of an error.
70 * @param Exception $exception exception raised
72 * @return void
74 public function storeError($description, $exception)
76 $this->runResult['errors'][] = $description
77 . ' '
78 . sprintf(
79 __('PHP threw following error: %s'),
80 $exception->getMessage()
84 /**
85 * Executes advisor rules
87 * @return boolean
89 public function runRules()
91 $this->runResult = array(
92 'fired' => array(),
93 'notfired' => array(),
94 'unchecked'=> array(),
95 'errors' => array()
98 foreach ($this->parseResult['rules'] as $rule) {
99 $this->variables['value'] = 0;
100 $precond = true;
102 if (isset($rule['precondition'])) {
103 try {
104 $precond = $this->ruleExprEvaluate($rule['precondition']);
105 } catch (Exception $e) {
106 $this->storeError(
107 sprintf(
108 __('Failed evaluating precondition for rule \'%s\'.'),
109 $rule['name']
113 continue;
117 if (! $precond) {
118 $this->addRule('unchecked', $rule);
119 } else {
120 try {
121 $value = $this->ruleExprEvaluate($rule['formula']);
122 } catch(Exception $e) {
123 $this->storeError(
124 sprintf(
125 __('Failed calculating value for rule \'%s\'.'),
126 $rule['name']
130 continue;
133 $this->variables['value'] = $value;
135 try {
136 if ($this->ruleExprEvaluate($rule['test'])) {
137 $this->addRule('fired', $rule);
138 } else {
139 $this->addRule('notfired', $rule);
141 } catch(Exception $e) {
142 $this->storeError(
143 sprintf(
144 __('Failed running test for rule \'%s\'.'),
145 $rule['name']
153 return true;
157 * Escapes percent string to be used in format string.
159 * @param string $str string to escape
161 * @return string
163 public static function escapePercent($str)
165 return preg_replace('/%( |,|\.|$|\(|\)|<|>)/', '%%\1', $str);
169 * Wrapper function for translating.
171 * @param string $str the string
172 * @param string $param the parameters
174 * @return string
176 public function translate($str, $param = null)
178 $string = _gettext(Advisor::escapePercent($str));
179 if ( ! is_null($param)) {
180 $params = $this->ruleExprEvaluate('array(' . $param . ')');
181 } else {
182 $params = array();
184 return vsprintf($string, $params);
188 * Splits justification to text and formula.
190 * @param array $rule the rule
192 * @return string[]
194 public static function splitJustification($rule)
196 $jst = preg_split('/\s*\|\s*/', $rule['justification'], 2);
197 if (count($jst) > 1) {
198 return array($jst[0], $jst[1]);
200 return array($rule['justification']);
204 * Adds a rule to the result list
206 * @param string $type type of rule
207 * @param array $rule rule itself
209 * @return void
211 public function addRule($type, $rule)
213 switch($type) {
214 case 'notfired':
215 case 'fired':
216 $jst = Advisor::splitJustification($rule);
217 if (count($jst) > 1) {
218 try {
219 /* Translate */
220 $str = $this->translate($jst[0], $jst[1]);
221 } catch (Exception $e) {
222 $this->storeError(
223 sprintf(
224 __('Failed formatting string for rule \'%s\'.'),
225 $rule['name']
229 return;
232 $rule['justification'] = $str;
233 } else {
234 $rule['justification'] = $this->translate($rule['justification']);
236 $rule['id'] = $rule['name'];
237 $rule['name'] = $this->translate($rule['name']);
238 $rule['issue'] = $this->translate($rule['issue']);
240 // Replaces {server_variable} with 'server_variable'
241 // linking to server_variables.php
242 $rule['recommendation'] = preg_replace(
243 '/\{([a-z_0-9]+)\}/Ui',
244 '<a href="server_variables.php' . PMA_URL_getCommon()
245 . '&filter=\1">\1</a>',
246 $this->translate($rule['recommendation'])
249 // Replaces external Links with PMA_linkURL() generated links
250 $rule['recommendation'] = preg_replace_callback(
251 '#href=("|\')(https?://[^\1]+)\1#i',
252 array($this, '_replaceLinkURL'),
253 $rule['recommendation']
255 break;
258 $this->runResult[$type][] = $rule;
262 * Callback for wrapping links with PMA_linkURL
264 * @param array $matches List of matched elements form preg_replace_callback
266 * @return string Replacement value
268 private function _replaceLinkURL($matches)
270 return 'href="' . PMA_linkURL($matches[2]) . '"';
274 * Callback for evaluating fired() condition.
276 * @param array $matches List of matched elements form preg_replace_callback
278 * @return string Replacement value
280 private function _ruleExprEvaluateFired($matches)
282 // No list of fired rules
283 if (!isset($this->runResult['fired'])) {
284 return '0';
287 // Did matching rule fire?
288 foreach ($this->runResult['fired'] as $rule) {
289 if ($rule['id'] == $matches[2]) {
290 return '1';
294 return '0';
298 * Callback for evaluating variables in expression.
300 * @param array $matches List of matched elements form preg_replace_callback
302 * @return string Replacement value
304 private function _ruleExprEvaluateVariable($matches)
306 if (! isset($this->variables[$matches[1]])) {
307 return $matches[1];
309 if (is_numeric($this->variables[$matches[1]])) {
310 return $this->variables[$matches[1]];
311 } else {
312 return '\'' . addslashes($this->variables[$matches[1]]) . '\'';
317 * Runs a code expression, replacing variable names with their respective
318 * values
320 * @param string $expr expression to evaluate
322 * @return integer result of evaluated expression
324 * @throws Exception
326 public function ruleExprEvaluate($expr)
328 // Evaluate fired() conditions
329 $expr = preg_replace_callback(
330 '/fired\s*\(\s*(\'|")(.*)\1\s*\)/Ui',
331 array($this, '_ruleExprEvaluateFired'),
332 $expr
334 // Evaluate variables
335 $expr = preg_replace_callback(
336 '/\b(\w+)\b/',
337 array($this, '_ruleExprEvaluateVariable'),
338 $expr
340 $value = 0;
341 $err = 0;
343 // Actually evaluate the code
344 ob_start();
345 try {
346 eval('$value = ' . $expr . ';');
347 $err = ob_get_contents();
348 } catch (Exception $e) {
349 // In normal operation, there is just output in the buffer,
350 // but when running under phpunit, error in eval raises exception
351 $err = $e->getMessage();
353 ob_end_clean();
355 // Error handling
356 if ($err) {
357 throw new Exception(
358 strip_tags($err)
359 . '<br />Executed code: $value = ' . htmlspecialchars($expr) . ';'
362 return $value;
366 * Reads the rule file into an array, throwing errors messages on syntax
367 * errors.
369 * @return array with parsed data
371 public static function parseRulesFile()
373 $file = file('libraries/advisory_rules.txt', FILE_IGNORE_NEW_LINES);
374 $errors = array();
375 $rules = array();
376 $lines = array();
377 $ruleSyntax = array(
378 'name', 'formula', 'test', 'issue', 'recommendation', 'justification'
380 $numRules = count($ruleSyntax);
381 $numLines = count($file);
382 $ruleNo = -1;
383 $ruleLine = -1;
385 for ($i = 0; $i < $numLines; $i++) {
386 $line = $file[$i];
387 if ($line == "" || $line[0] == '#') {
388 continue;
391 // Reading new rule
392 if (substr($line, 0, 4) == 'rule') {
393 if ($ruleLine > 0) {
394 $errors[] = sprintf(
396 'Invalid rule declaration on line %1$s, expected line '
397 . '%2$s of previous rule.'
399 $i + 1,
400 $ruleSyntax[$ruleLine++]
402 continue;
404 if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/", $line, $match)) {
405 $ruleLine = 1;
406 $ruleNo++;
407 $rules[$ruleNo] = array('name' => $match[1]);
408 $lines[$ruleNo] = array('name' => $i + 1);
409 if (isset($match[3])) {
410 $rules[$ruleNo]['precondition'] = $match[3];
411 $lines[$ruleNo]['precondition'] = $i + 1;
413 } else {
414 $errors[] = sprintf(
415 __('Invalid rule declaration on line %s.'),
416 $i + 1
419 continue;
420 } else {
421 if ($ruleLine == -1) {
422 $errors[] = sprintf(
423 __('Unexpected characters on line %s.'),
424 $i + 1
429 // Reading rule lines
430 if ($ruleLine > 0) {
431 if (!isset($line[0])) {
432 continue; // Empty lines are ok
434 // Non tabbed lines are not
435 if ($line[0] != "\t") {
436 $errors[] = sprintf(
438 'Unexpected character on line %1$s. Expected tab, but '
439 . 'found "%2$s".'
441 $i + 1,
442 $line[0]
444 continue;
446 $rules[$ruleNo][$ruleSyntax[$ruleLine]] = chop(
447 /*overload*/mb_substr($line, 1)
449 $lines[$ruleNo][$ruleSyntax[$ruleLine]] = $i + 1;
450 $ruleLine += 1;
453 // Rule complete
454 if ($ruleLine == $numRules) {
455 $ruleLine = -1;
459 return array('rules' => $rules, 'lines' => $lines, 'errors' => $errors);
465 * Formats interval like 10 per hour
467 * @param integer $num number to format
468 * @param integer $precision required precision
470 * @return string formatted string
472 function ADVISOR_bytime($num, $precision)
474 if ($num >= 1) { // per second
475 $per = __('per second');
476 } elseif ($num * 60 >= 1) { // per minute
477 $num = $num * 60;
478 $per = __('per minute');
479 } elseif ($num * 60 * 60 >= 1 ) { // per hour
480 $num = $num * 60 * 60;
481 $per = __('per hour');
482 } else {
483 $num = $num * 60 * 60 * 24;
484 $per = __('per day');
487 $num = round($num, $precision);
489 if ($num == 0) {
490 $num = '<' . PMA_Util::pow(10, -$precision);
493 return "$num $per";
497 * Wrapper for PMA_Util::timespanFormat
499 * This function is used when evaluating advisory_rules.txt
501 * @param int $seconds the timespan
503 * @return string the formatted value
505 function ADVISOR_timespanFormat($seconds)
507 return PMA_Util::timespanFormat($seconds);
511 * Wrapper around PMA_Util::formatByteDown
513 * This function is used when evaluating advisory_rules.txt
515 * @param double $value the value to format
516 * @param int $limes the sensitiveness
517 * @param int $comma the number of decimals to retain
519 * @return string the formatted value with unit
521 function ADVISOR_formatByteDown($value, $limes = 6, $comma = 0)
523 return implode(' ', PMA_Util::formatByteDown($value, $limes, $comma));