Completed
Push — 7.x-1.x ( 027296...66a87c )
by Frédéric G.
05:26
created

qa.module (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @file
5
 * OSInet Quality Assurance module for Drupal.
6
 *
7
 * @copyright Copyright (C) 2005-2018 Frederic G. MARAND for Ouest Systèmes Informatiques (OSInet)
8
 *
9
 * @since DRUPAL-4-6
10
 *
11
 * @license Licensed under the disjunction of the CeCILL, version 2 and General Public License version 2 and later
12
 *
13
 * License note: QA is distributed by OSInet to its customers under the
14
 * CeCILL 2.0 license. OSInet support services only apply to the module
15
 * when distributed by OSInet, not by any third-party further down the
16
 * distribution chain.
17
 *
18
 * If you obtained QA from drupal.org, that site received it under the
19
 * GPLv2 license and can therefore distribute it under the GPLv2, and
20
 * so can you and just anyone down the chain as long as the GPLv2 terms
21
 * are abided by, the module distributor in that case being the
22
 * drupal.org organization or the downstream distributor, not OSInet.
23
 */
24
25
use Drupal\qa\Exportable;
26
use Drupal\qa\Plugin\Qa\Control\BaseControl;
27
use Drupal\qa\Plugin\Qa\Control\BasePackage;
28
use Drupal\qa\Plugin\Qa\Control\Variable\Variable;
29
30
/**
31
 * Helper to access the module files.
32
 *
33
 * @param string $extra
34
 *   Optional. A module sub-component.
35
 *
36
 * @return string
37
 *   The on-disk path.
38
 */
39
function _qa_get_path(string $extra = '') {
40
  $qa = drupal_get_path('module', 'qa');
41
  return implode('/', [$qa, $extra]);
42
}
43
44
/**
45
 * Implements hook_boot().
46
 *
47
 * Menu loaders may need objects before hook_init().
48
 *
49
 * - register custom autoloader.
50
 */
51
function qa_boot() {
52
  spl_autoload_register(qa_autoload_psr4::class);
53
}
54
55
/**
56
 * Legacy (pre-PSR/4) Dedicated autoloader for QA.
57
 *
58
 * Only load symbols in the Drupal\qa namespace.
59
 *
60
 * @param string $name
61
 *   The symbol to load.
62
 *
63
 * @deprecated
64
 */
65
function qa_autoload($name) {
66
  // Adjust verbosity if needed.
67
  $verbose = FALSE;
68
69
  $verbose && watchdog('qa/autoload', 'Loading %name', array('%name' => $name), WATCHDOG_DEBUG);
70
  if (strpos($name, 'Drupal\qa\\') !== 0) {
71
    return;
72
  }
73
74
  $path_array = explode('\\', $name);
75
  $filename = array_pop($path_array);
76
  array_splice($path_array, 0, 2, [_qa_get_path(), 'src/Plugin/Qa/Control']);
77
  $path = implode('/', $path_array);
78
  if (!is_dir($path) || !is_readable($path)) {
79
    $args = array('%path' => $path);
80
    drupal_set_message(t("Cannot read plugins directory %path.", $args), 'warning');
81
    watchdog('qa', "Cannot read plugins directory %path", $args, WATCHDOG_WARNING);
82
  }
83
  $path_array[] = "{$filename}.php";
84
  $path = implode('/', $path_array);
85
  $sts = include_once $path;
86
  $verbose && drupal_set_message(t('QA Autoloaded %path: @result', array(
87
    '%path' => $path,
88
    '@result' => $sts ? t('Success') : t('Failure'),
89
  )));
90
}
91
92
/**
93
 * Dedicated PSR/4 autoloader for this module.
94
 *
95
 * @param string $class
96
 *   The name of the class to load.
97
 *
98
 * @see \qa_boot()
99
 */
100
function qa_autoload_psr4($class) {
101
  // Project-specific namespace prefix.
102
  $prefix = 'Drupal\\qa\\';
103
104
  // Does the class use the QA namespace prefix ?
105
  $len = strlen($prefix);
106
  if (strncmp($prefix, $class, $len) !== 0) {
107
    // No: move to the next registered autoloader.
108
    return;
109
  }
110
111
  // Get the relative class name.
112
  $relative_class = substr($class, $len);
113
114
  // Replace the namespace prefix with the base directory, replace namespace
115
  // separators with directory separators in the relative class name, append
116
  // with ".php".
117
  $file = __DIR__ . '/src/' . str_replace('\\', '/', $relative_class) . '.php';
118
119
  // If the file exists, require it.
120
  if (file_exists($file)) {
121
    require $file;
122
  }
123
}
124
125
/**
126
 * Implements hook_menu().
127
 */
128
function qa_menu() {
129
  $items = array();
130
  $items['admin/reports/qa'] = array(
131
    'title'            => 'Quality Assurance',
132
    'description'      => 'Assisted auditing tools by OSInet',
133
    'page callback'    => 'drupal_get_form',
134
    'page arguments'   => array('qa_report_form'),
135
    'access arguments' => array('access site reports'),
136
  );
137
  $items['admin/reports/qa/projects'] = array(
138
    'title'           => 'Projects',
139
    'type'             => MENU_LOCAL_TASK,
140
    'page callback'    => 'qa_report_projects',
141
    'access arguments' => array('access site reports'),
142
    'file'             => 'qa_projects.inc',
143
  );
144
  $items['admin/reports/qa/variable'] = array(
145
    'title'           => 'Variables',
146
    'type'             => MENU_LOCAL_TASK,
147
    'page callback'    => 'qa_report_variables',
148
    'access arguments' => array('access site reports'),
149
    'file'             => 'qa_variables.inc',
150
  );
151
  $items['admin/reports/qa/variable/%qa_variable'] = array(
152
    'title'           => 'Variables',
153
    'type'             => MENU_CALLBACK,
154
    'page callback'    => 'qa_report_variable',
155
    'page arguments'   => array(4),
156
    'access arguments' => array('access site reports'),
157
    'file'             => 'qa_variables.inc',
158
  );
159
160
  $items['admin/reports/qa/results'] = array(
161
    'title'            => 'Quality Assurance results',
162
    'page callback'    => 'qa_report_results',
163
    'page arguments'   => array(),
164
    'access arguments' => array('access site reports'),
165
    'type'             => MENU_CALLBACK,
166
  );
167
  $items['admin/reports/qa/list'] = array(
168
    'title'            => 'QA Tests',
169
    'type'             => MENU_DEFAULT_LOCAL_TASK,
170
  );
171
  $items['admin/reports/qa/dependencies'] = array(
172
    'title'            => 'Dependencies',
173
    'type'             => MENU_LOCAL_TASK,
174
    'page callback'    => 'qa_page_dependencies',
175
    'access arguments' => array('access site reports'),
176
    'file'             => 'qa_dependencies.inc',
177
    'weight'           => 1,
178
  );
179
  return $items;
180
}
181
182
/**
183
 * Page callback for qa/dependencies.
184
 *
185
 * TODO convert to Image_GraphViz to remove dependency on graphviz_filter.
186
 * XXX convert to Grafizzi to remove dependency on Image_GraphViz.
187
 *
188
 * @return string
189
 *   The dependencies graph, in DOT format.
190
 */
191
function qa_page_dependencies() {
192
  $graph = qa_dependencies();
193
  // Passed by reference: cannot pass a function return.
194
  return graphviz_filter_render($graph);
195
}
196
197
/**
198
 * Batch conclusion callback.
199
 *
200
 * @param bool $success
201
 *   Did the batch succeed ?
202
 * @param array $results
203
 *   The accumulated batch results.
204
 * @param array $operations
205
 *   The batch operations.
206
 */
207
function qa_report_finished(bool $success, array $results, array $operations) {
0 ignored issues
show
The parameter $operations is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
208
  unset($results['#message']);
209
  if ($success) {
210
    $message = format_plural(count($results), 'One control pass ran.', '@count control passes ran.');
211
  }
212
  else {
213
    $message = t('Finished with an error.');
214
  }
215
  drupal_set_message($message);
216
  $_SESSION['qa_results'] = $results;
217
  drupal_goto('admin/reports/qa/results');
218
}
219
220
/**
221
 * Results page for QA Controls batch.
222
 *
223
 * @link http://www.php.net/manual/fr/function.unserialize.php @endlink
224
 */
225
function qa_report_results() {
226
  if (empty($_SESSION['qa_results'])) {
227
    drupal_goto('admin/reports/qa');
228
  }
229
  // Work around incomplete classes.
230
  $results = unserialize(serialize($_SESSION['qa_results']));
231
232
  $header = [
233
    t('Control'),
234
    t('Status'),
235
    t('Results'),
236
  ];
237
  $data = [];
238
  foreach ($results as $pass) {
239
    $control = $pass->control;
240
    $data[] = [
241
      $control->title,
242
      $pass->status
243
      ? theme('image', [
244
        'path' => 'misc/watchdog-ok.png',
245
        'alt' => t('OK'),
246
      ])
247
      : theme('image', [
248
        'path' => 'misc/watchdog-error.png',
249
        'alt' => t('Error'),
250
      ]),
251
      $pass->result,
252
    ];
253
  }
254
255
  $ret = [
256
    '#theme' => 'table',
257
    '#header' => $header,
258
    '#rows' => $data,
259
    '#attributes' => [
260
      'id' => 'qa-results',
261
    ],
262
    '#attached' => [
263
      'css' => [
264
        _qa_get_path('qa.css'),
265
      ],
266
    ],
267
  ];
268
269
  // Do unset($_SESSION['qa_results']) to allow refreshing the results page.
270
  return $ret;
271
}
272
273
/**
274
 * Form builder for QA packages/controls selection form.
275
 *
276
 * @return array
277
 *   The form array.
278
 */
279
function qa_report_form(array $form, array $form_state) {
0 ignored issues
show
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $form_state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
280
  $form = array();
281
  $packages = Exportable::getClasses(_qa_get_path(), BasePackage::class);
282
  ksort($packages);
283
  foreach ($packages as $package_name => $package) {
284
    $collapsed = TRUE;
285
    $form[$package_name] = array(
286
      '#type' => 'fieldset',
287
      '#title' => filter_xss_admin($package->title),
288
      '#description' => filter_xss_admin($package->description),
289
      '#collapsible' => TRUE,
290
    );
291
    $controls = $package->getClasses($package->dir, BaseControl::class);
292
293
    foreach ($controls as $control_name => $control) {
294
      $default_value = isset($_SESSION[$control_name])
295
        ? $_SESSION[$control_name]
296
        : NULL;
297
      if ($default_value) {
298
        $collapsed = FALSE;
299
      }
300
301
      $deps = array();
302
      $met = TRUE;
303
      foreach ($control->getDependencies() as $dep_name) {
304
        if (module_exists($dep_name)) {
305
          $deps[] = t('@module (<span class="admin-enabled">available</span>)', ['@module' => $dep_name]);
306
        }
307
        else {
308
          $deps[] = t('@module (<span class="admin-disabled">unavailable</span>)', ['@module' => $dep_name]);
309
          $met = FALSE;
310
        }
311
      }
312
      $form[$package_name][$control_name] = [
313
        '#type'          => 'checkbox',
314
        '#default_value' => $met ? $default_value : 0,
315
        '#title'         => filter_xss_admin($control->title),
316
        '#description'   => filter_xss_admin($control->description),
317
        '#disabled'      => !$met,
318
      ];
319
      $form[$package_name][$control_name . '-dependencies'] = [
320
        '#value' => t('Depends on: !dependencies', [
321
          '!dependencies' => implode(', ', $deps),
322
        ]),
323
        '#prefix' => '<div class="admin-dependencies">',
324
        '#suffix' => '</div>',
325
      ];
326
    }
327
    $form[$package_name]['#collapsed'] = $collapsed;
328
  }
329
330
  $form['submit'] = [
331
    '#type'  => 'submit',
332
    '#value' => t('Run controls'),
333
  ];
334
335
  return $form;
336
}
337
338
/**
339
 * Submit handler for QA packages/controls selection form.
340
 *
341
 * @param array $form
342
 *   The submitted form.
343
 * @param array $form_state
344
 *   Its data.
345
 */
346
function qa_report_form_submit(array $form, array &$form_state) {
0 ignored issues
show
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
347
  $controls = [];
348
  foreach ($form_state['values'] as $item => $value) {
349
    if (class_exists($item) && is_subclass_of($item, BaseControl::class)) {
0 ignored issues
show
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Drupal\qa\Plugin\Qa\Control\BaseControl::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
350
      if ($value) {
351
        $controls[$item] = $value;
352
      }
353
      $_SESSION[$item] = $value;
354
    }
355
    elseif ($value == 1) {
356
      $args = array(
357
        '%control' => $item,
358
      );
359
      drupal_set_message(t('Requested invalid control %control', $args), 'error');
360
      watchdog('qa', 'Requested invalid control %control', $args, WATCHDOG_ERROR);
361
    }
362
  }
363
364
  drupal_set_message(t('Prepare to run these controls: @controls', [
365
    '@controls' => implode(', ', array_keys($controls)),
366
  ]), 'status');
367
  $batch = array(
368
    'operations'       => array(),
369
    'title'            => t('QA Controls running'),
370
    'init_message'     => t('QA Controls initializing'),
371
    // 'progress_message' =>
372
    // t('current: @current, Remaining: @remaining, Total: @total'),
373
    'error_message'    => t('Error in QA Control'),
374
    'finished'         => 'qa_report_finished',
375
    // 'file'             => '', // only if outside module file.
376
  );
377
378
  foreach ($controls as $item => $value) {
379
    $batch['operations'][] = array('qa_report_run_pass', array($item));
380
  }
381
  batch_set($batch);
382
}
383
384
/**
385
 * Batch progress step.
386
 */
387
function qa_report_run_pass($class_name, &$context) {
388
  $name_arg = array('@class' => $class_name);
389
390
  $control = new $class_name();
391
  if (!is_object($control)) {
392
    drupal_set_message(t('Cannot obtain an instance for @class', $name_arg), 'error');
393
    $context['results']['#message'] = t('Control @class failed to run.', $name_arg);
394
    $context['message'] = t('Control @class failed to run.', $name_arg);
395
    $context['results'][$class_name] = 'wow';
396
  }
397
  else {
398
    drupal_set_message(t('Running a control instance for @class', $name_arg), 'status');
399
    $pass = $control->run();
400
    if (!$pass->status) {
401
      $context['success'] = FALSE;
402
    }
403
    $context['results']['#message'][] = t('Control @class ran', $name_arg);
404
    $context['message'] = theme('item_list', $context['results']['#message']);
405
    $context['results'][$class_name] = $pass;
406
  }
407
}
408
409
/**
410
 * Use a Drupal variable as a Variable instance if it is set.
411
 *
412
 * @param string $name
413
 *   The name of the variable.
414
 *
415
 * @return bool|\Drupal\qa\Plugin\Qa\Control\Variable\Variable
416
 *   An associated Variable instance, or FALSE if it does not exist.
417
 */
418
function qa_variable_load($name) {
419
  $variable = new Variable($name);
420
  if (!$variable->is_set) {
421
    return FALSE;
422
  }
423
424
  return $variable;
425
}
426