Merge branch 'QA_3_4'
[phpmyadmin/alexukf.git] / libraries / Advisor.class.php
blobf5ed188c0254569d75997e8ac22795ab86747de3
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. Adjusted to phpMyAdmin
7 * @package PhpMyAdmin
8 */
10 class Advisor
12 var $variables;
13 var $parseResult;
14 var $runResult;
16 function run()
18 // HowTo: A simple Advisory system in 3 easy steps.
20 // Step 1: Get some variables to evaluate on
21 $this->variables = array_merge(
22 PMA_DBI_fetch_result('SHOW GLOBAL STATUS', 0, 1),
23 PMA_DBI_fetch_result('SHOW GLOBAL VARIABLES', 0, 1)
25 if (PMA_DRIZZLE) {
26 $this->variables = array_merge(
27 $this->variables,
28 PMA_DBI_fetch_result(
29 "SELECT concat('Com_', variable_name), variable_value
30 FROM data_dictionary.GLOBAL_STATEMENTS", 0, 1
34 // Add total memory to variables as well
35 include_once 'libraries/sysinfo.lib.php';
36 $sysinfo = getSysInfo();
37 $memory = $sysinfo->memory();
38 $this->variables['system_memory'] = $memory['MemTotal'];
40 // Step 2: Read and parse the list of rules
41 $this->parseResult = $this->parseRulesFile();
42 // Step 3: Feed the variables to the rules and let them fire. Sets $runResult
43 $this->runRules();
45 return array(
46 'parse' => array('errors' => $this->parseResult['errors']),
47 'run' => $this->runResult
51 function runRules()
53 $this->runResult = array(
54 'fired' => array(),
55 'notfired' => array(),
56 'unchecked'=> array(),
57 'errors' => array()
60 foreach ($this->parseResult['rules'] as $rule) {
61 $this->variables['value'] = 0;
62 $precond = true;
64 if (isset($rule['precondition'])) {
65 try {
66 $precond = $this->ruleExprEvaluate($rule['precondition']);
67 } catch (Exception $e) {
68 $this->runResult['errors'][] = 'Failed evaluating precondition for rule \''
69 . $rule['name'] . '\'. PHP threw following error: '
70 . $e->getMessage();
71 continue;
75 if (! $precond) {
76 $this->addRule('unchecked', $rule);
77 } else {
78 try {
79 $value = $this->ruleExprEvaluate($rule['formula']);
80 } catch(Exception $e) {
81 $this->runResult['errors'][] = 'Failed calculating value for rule \''
82 . $rule['name'] . '\'. PHP threw following error: '
83 . $e->getMessage();
84 continue;
87 $this->variables['value'] = $value;
89 try {
90 if ($this->ruleExprEvaluate($rule['test'])) {
91 $this->addRule('fired', $rule);
92 } else {
93 $this->addRule('notfired', $rule);
95 } catch(Exception $e) {
96 $this->runResult['errors'][] = 'Failed running test for rule \''
97 . $rule['name'] . '\'. PHP threw following error: '
98 . $e->getMessage();
103 return true;
107 * Escapes percent string to be used in format string.
109 * @param string $str string to escape
111 * @return string
113 function escapePercent($str)
115 return preg_replace('/%( |,|\.|$)/', '%%\1', $str);
119 * Wrapper function for translating.
121 * @param string $str
122 * @param mixed $param
124 * @return string
126 function translate($str, $param = null)
128 if (is_null($param)) {
129 return sprintf(_gettext(Advisor::escapePercent($str)));
130 } else {
131 $printf = 'sprintf("' . _gettext(Advisor::escapePercent($str)) . '",';
132 return $this->ruleExprEvaluate(
133 $printf . $param . ')',
134 strlen($printf)
140 * Splits justification to text and formula.
142 * @param string $rule
144 * @return array
146 function splitJustification($rule)
148 $jst = preg_split('/\s*\|\s*/', $rule['justification'], 2);
149 if (count($jst) > 1) {
150 return array($jst[0], $jst[1]);
152 return array($rule['justification']);
155 // Adds a rule to the result list
156 function addRule($type, $rule)
158 switch($type) {
159 case 'notfired':
160 case 'fired':
161 $jst = Advisor::splitJustification($rule);
162 if (count($jst) > 1) {
163 try {
164 /* Translate */
165 $str = $this->translate($jst[0], $jst[1]);
166 } catch (Exception $e) {
167 $this->runResult['errors'][] = sprintf(
168 __('Failed formatting string for rule \'%s\'. PHP threw following error: %s'),
169 $rule['name'],
170 $e->getMessage()
172 return;
175 $rule['justification'] = $str;
176 } else {
177 $rule['justification'] = $this->translate($rule['justification']);
179 $rule['name'] = $this->translate($rule['name']);
180 $rule['issue'] = $this->translate($rule['issue']);
182 // Replaces {server_variable} with 'server_variable'
183 // linking to server_variables.php
184 $rule['recommendation'] = preg_replace(
185 '/\{([a-z_0-9]+)\}/Ui',
186 '<a href="server_variables.php?' . PMA_generate_common_url() . '#filter=\1">\1</a>',
187 $this->translate($rule['recommendation'])
190 // Replaces external Links with PMA_linkURL() generated links
191 $rule['recommendation'] = preg_replace(
192 '#href=("|\')(https?://[^\1]+)\1#ie',
193 '\'href="\' . PMA_linkURL("\2") . \'"\'',
194 $rule['recommendation']
196 break;
199 $this->runResult[$type][] = $rule;
202 private function ruleExprEvaluate_var1($matches)
204 // '/fired\s*\(\s*(\'|")(.*)\1\s*\)/Uie'
205 return '1'; //isset($this->runResult[\'fired\']
208 private function ruleExprEvaluate_var2($matches)
210 // '/\b(\w+)\b/e'
211 return isset($this->variables[$matches[1]])
212 ? (is_numeric($this->variables[$matches[1]])
213 ? $this->variables[$matches[1]]
214 : '"'.$this->variables[$matches[1]].'"')
215 : $matches[1];
218 // Runs a code expression, replacing variable names with their respective values
219 // ignoreUntil: if > 0, it doesn't replace any variables until that string
220 // position, but still evaluates the whole expr
221 function ruleExprEvaluate($expr, $ignoreUntil = 0)
223 if ($ignoreUntil > 0) {
224 $exprIgnore = substr($expr, 0, $ignoreUntil);
225 $expr = substr($expr, $ignoreUntil);
227 $expr = preg_replace_callback(
228 '/fired\s*\(\s*(\'|")(.*)\1\s*\)/Ui',
229 array($this, 'ruleExprEvaluate_var1'),
230 $expr
232 $expr = preg_replace_callback(
233 '/\b(\w+)\b/',
234 array($this, 'ruleExprEvaluate_var2'),
235 $expr
237 if ($ignoreUntil > 0) {
238 $expr = $exprIgnore . $expr;
240 $value = 0;
241 $err = 0;
243 ob_start();
244 eval('$value = '.$expr.';');
245 $err = ob_get_contents();
246 ob_end_clean();
247 if ($err) {
248 throw new Exception(
249 strip_tags($err) . '<br />Executed code: $value = ' . $expr . ';'
252 return $value;
255 // Reads the rule file into an array, throwing errors messages on syntax errors
256 function parseRulesFile()
258 $file = file('libraries/advisory_rules.txt');
259 $errors = array();
260 $rules = array();
261 $ruleSyntax = array('name', 'formula', 'test', 'issue', 'recommendation', 'justification');
262 $numRules = count($ruleSyntax);
263 $numLines = count($file);
264 $j = -1;
265 $ruleLine = -1;
267 for ($i = 0; $i<$numLines; $i++) {
268 $line = $file[$i];
269 if ($line[0] == '#' || $line[0] == "\n") {
270 continue;
273 // Reading new rule
274 if (substr($line, 0, 4) == 'rule') {
275 if ($ruleLine > 0) {
276 $errors[] = 'Invalid rule declaration on line ' . ($i+1)
277 . ', expected line ' . $ruleSyntax[$ruleLine++]
278 . ' of previous rule' ;
279 continue;
281 if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/", $line, $match)) {
282 $ruleLine = 1;
283 $j++;
284 $rules[$j] = array( 'name' => $match[1]);
285 if (isset($match[3])) {
286 $rules[$j]['precondition'] = $match[3];
288 } else {
289 $errors[] = 'Invalid rule declaration on line '.($i+1);
291 continue;
292 } else {
293 if ($ruleLine == -1) {
294 $errors[] = 'Unexpected characters on line '.($i+1);
298 // Reading rule lines
299 if ($ruleLine > 0) {
300 if (!isset($line[0])) {
301 continue; // Empty lines are ok
303 // Non tabbed lines are not
304 if ($line[0] != "\t") {
305 $errors[] = 'Unexpected character on line '.($i+1).'
306 . Expected tab, but found \''.$line[0].'\'';
307 continue;
309 $rules[$j][$ruleSyntax[$ruleLine++]] = chop(substr($line, 1));
312 // Rule complete
313 if ($ruleLine == $numRules) {
314 $ruleLine = -1;
318 return array('rules' => $rules, 'errors' => $errors);
322 function PMA_bytime($num, $precision)
324 $per = '';
325 if ($num >= 1) { // per second
326 $per = "per second";
327 } elseif ($num*60 >= 1) { // per minute
328 $num = $num*60;
329 $per = "per minute";
330 } elseif ($num*60*60 >=1 ) { // per hour
331 $num = $num*60*60;
332 $per = "per hour";
333 } else {
334 $num = $num*60*60*24;
335 $per = "per day";
338 $num = round($num, $precision);
340 if ($num == 0) {
341 $num = '<' . pow(10, -$precision);
344 return "$num $per";