configGenerator.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. #!/usr/bin/env php
  2. <?php
  3. /**
  4. * generates a config unit test class
  5. *
  6. * This generator is meant to test all possible configuration combinations
  7. * without having to write endless amounts of code manually.
  8. *
  9. * DANGER: Too many options/settings and too high max iteration setting may trigger
  10. * a fork bomb. Please save your work before executing this script.
  11. */
  12. include 'bootstrap.php';
  13. $vrd = array('view', 'read', 'delete');
  14. $vcud = array('view', 'create', 'read', 'delete');
  15. new configurationTestGenerator(array(
  16. 'main/opendiscussion' => array(
  17. array(
  18. 'setting' => true,
  19. 'tests' => array(
  20. array(
  21. 'conditions' => array('steps' => $vrd),
  22. 'type' => 'NotTag',
  23. 'args' => array(
  24. array(
  25. 'id' => 'opendiscussion',
  26. 'attributes' => array(
  27. 'disabled' => 'disabled',
  28. ),
  29. ),
  30. '$content',
  31. 'outputs enabled discussion correctly'
  32. ),
  33. ), array(
  34. 'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
  35. 'settings' => array('$_POST["opendiscussion"] = "neither 1 nor 0"'),
  36. 'type' => 'Equals',
  37. 'args' => array(
  38. 1,
  39. '$response["status"]',
  40. 'when discussions are enabled, but invalid flag posted, fail to create paste'
  41. ),
  42. ), array(
  43. 'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
  44. 'settings' => array('$_POST["opendiscussion"] = "neither 1 nor 0"'),
  45. 'type' => 'False',
  46. 'args' => array(
  47. '$this->_model->exists(self::$pasteid)',
  48. 'when discussions are enabled, but invalid flag posted, paste is not created'
  49. ),
  50. ),
  51. ),
  52. 'affects' => $vcud
  53. ), array(
  54. 'setting' => false,
  55. 'tests' => array(
  56. array(
  57. 'type' => 'Tag',
  58. 'args' => array(
  59. array(
  60. 'id' => 'opendiscussion',
  61. 'attributes' => array(
  62. 'disabled' => 'disabled',
  63. ),
  64. ),
  65. '$content',
  66. 'outputs disabled discussion correctly'
  67. ),
  68. ),
  69. ),
  70. 'affects' => $vrd
  71. ),
  72. ),
  73. 'main/syntaxhighlighting' => array(
  74. array(
  75. 'setting' => true,
  76. 'tests' => array(
  77. array(
  78. 'type' => 'Tag',
  79. 'args' => array(
  80. array(
  81. 'tag' => 'link',
  82. 'attributes' => array(
  83. 'type' => 'text/css',
  84. 'rel' => 'stylesheet',
  85. 'href' => 'regexp:#css/prettify/prettify\.css#',
  86. ),
  87. ),
  88. '$content',
  89. 'outputs prettify stylesheet correctly',
  90. ),
  91. ), array(
  92. 'type' => 'Tag',
  93. 'args' => array(
  94. array(
  95. 'tag' => 'script',
  96. 'attributes' => array(
  97. 'type' => 'text/javascript',
  98. 'src' => 'regexp:#js/prettify\.js#'
  99. ),
  100. ),
  101. '$content',
  102. 'outputs prettify javascript correctly',
  103. ),
  104. ),
  105. ),
  106. 'affects' => $vrd,
  107. ), array(
  108. 'setting' => false,
  109. 'tests' => array(
  110. array(
  111. 'type' => 'NotTag',
  112. 'args' => array(
  113. array(
  114. 'tag' => 'link',
  115. 'attributes' => array(
  116. 'type' => 'text/css',
  117. 'rel' => 'stylesheet',
  118. 'href' => 'regexp:#css/prettify/prettify\.css#',
  119. ),
  120. ),
  121. '$content',
  122. 'removes prettify stylesheet correctly',
  123. ),
  124. ), array(
  125. 'type' => 'NotTag',
  126. 'args' => array(
  127. array(
  128. 'tag' => 'script',
  129. 'attributes' => array(
  130. 'type' => 'text/javascript',
  131. 'src' => 'regexp:#js/prettify\.js#',
  132. ),
  133. ),
  134. '$content',
  135. 'removes prettify javascript correctly',
  136. ),
  137. ),
  138. ),
  139. 'affects' => $vrd,
  140. ),
  141. ),
  142. 'main/syntaxhighlightingtheme' => array(
  143. array(
  144. 'setting' => 'sons-of-obsidian',
  145. 'tests' => array(
  146. array(
  147. 'conditions' => array('main/syntaxhighlighting' => true),
  148. 'type' => 'Tag',
  149. 'args' => array(
  150. array(
  151. 'tag' => 'link',
  152. 'attributes' => array(
  153. 'type' => 'text/css',
  154. 'rel' => 'stylesheet',
  155. 'href' => 'regexp:#css/prettify/sons-of-obsidian\.css#',
  156. ),
  157. ),
  158. '$content',
  159. 'outputs prettify theme stylesheet correctly',
  160. ),
  161. ), array(
  162. 'conditions' => array('main/syntaxhighlighting' => false),
  163. 'type' => 'NotTag',
  164. 'args' => array(
  165. array(
  166. 'tag' => 'link',
  167. 'attributes' => array(
  168. 'type' => 'text/css',
  169. 'rel' => 'stylesheet',
  170. 'href' => 'regexp:#css/prettify/sons-of-obsidian\.css#',
  171. ),
  172. ),
  173. '$content',
  174. 'removes prettify theme stylesheet correctly',
  175. ),
  176. ),
  177. ),
  178. 'affects' => $vrd,
  179. ), array(
  180. 'setting' => null, // option not set
  181. 'tests' => array(
  182. array(
  183. 'type' => 'NotTag',
  184. 'args' => array(
  185. array(
  186. 'tag' => 'link',
  187. 'attributes' => array(
  188. 'type' => 'text/css',
  189. 'rel' => 'stylesheet',
  190. 'href' => 'regexp:#css/prettify/sons-of-obsidian\.css#',
  191. ),
  192. ),
  193. '$content',
  194. 'removes prettify theme stylesheet correctly',
  195. ),
  196. ),
  197. ),
  198. 'affects' => $vrd,
  199. ),
  200. ),
  201. 'main/burnafterreadingselected' => array(
  202. array(
  203. 'setting' => true,
  204. 'tests' => array(
  205. array(
  206. 'type' => 'Tag',
  207. 'args' => array(
  208. array(
  209. 'id' => 'burnafterreading',
  210. 'attributes' => array(
  211. 'checked' => 'checked',
  212. ),
  213. ),
  214. '$content',
  215. 'preselects burn after reading option',
  216. ),
  217. ),
  218. ),
  219. 'affects' => array('view'),
  220. ), array(
  221. 'setting' => false,
  222. 'tests' => array(
  223. array(
  224. 'type' => 'NotTag',
  225. 'args' => array(
  226. array(
  227. 'id' => 'burnafterreading',
  228. 'attributes' => array(
  229. 'checked' => 'checked',
  230. ),
  231. ),
  232. '$content',
  233. 'burn after reading option is unchecked',
  234. ),
  235. ),
  236. ),
  237. 'affects' => array('view'),
  238. ),
  239. ),
  240. 'main/template' => array(
  241. array(
  242. 'setting' => 'page',
  243. 'tests' => array(
  244. array(
  245. 'type' => 'Tag',
  246. 'args' => array(
  247. array(
  248. 'tag' => 'link',
  249. 'attributes' => array(
  250. 'type' => 'text/css',
  251. 'rel' => 'stylesheet',
  252. 'href' => 'regexp:#css/zerobin\.css#',
  253. ),
  254. ),
  255. '$content',
  256. 'outputs "page" stylesheet correctly',
  257. ),
  258. ), array(
  259. 'type' => 'NotTag',
  260. 'args' => array(
  261. array(
  262. 'tag' => 'link',
  263. 'attributes' => array(
  264. 'type' => 'text/css',
  265. 'rel' => 'stylesheet',
  266. 'href' => 'regexp:#css/bootstrap/bootstrap-\d[\d\.]+\d\.css#',
  267. ),
  268. ),
  269. '$content',
  270. 'removes "bootstrap" stylesheet correctly',
  271. ),
  272. ),
  273. ),
  274. 'affects' => $vrd,
  275. ), array(
  276. 'setting' => 'bootstrap',
  277. 'tests' => array(
  278. array(
  279. 'type' => 'NotTag',
  280. 'args' => array(
  281. array(
  282. 'tag' => 'link',
  283. 'attributes' => array(
  284. 'type' => 'text/css',
  285. 'rel' => 'stylesheet',
  286. 'href' => 'regexp:#css/zerobin.css#',
  287. ),
  288. ),
  289. '$content',
  290. 'removes "page" stylesheet correctly',
  291. ),
  292. ), array(
  293. 'type' => 'Tag',
  294. 'args' => array(
  295. array(
  296. 'tag' => 'link',
  297. 'attributes' => array(
  298. 'type' => 'text/css',
  299. 'rel' => 'stylesheet',
  300. 'href' => 'regexp:#css/bootstrap/bootstrap-\d[\d\.]+\d\.css#',
  301. ),
  302. ),
  303. '$content',
  304. 'outputs "bootstrap" stylesheet correctly',
  305. ),
  306. ),
  307. ),
  308. 'affects' => $vrd,
  309. ),
  310. ),
  311. 'main/sizelimit' => array(
  312. array(
  313. 'setting' => 10,
  314. 'tests' => array(
  315. array(
  316. 'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
  317. 'type' => 'Equals',
  318. 'args' => array(
  319. 1,
  320. '$response["status"]',
  321. 'when sizelimit limit exceeded, fail to create paste'
  322. ),
  323. ),
  324. ),
  325. 'affects' => array('create')
  326. ), array(
  327. 'setting' => 2097152,
  328. 'tests' => array(
  329. array(
  330. 'conditions' => array('steps' => array('create'), 'traffic/limit' => 0, 'main/burnafterreadingselected' => true),
  331. 'settings' => array('sleep(3)'),
  332. 'type' => 'Equals',
  333. 'args' => array(
  334. 0,
  335. '$response["status"]',
  336. 'when sizelimit limit is not reached, successfully create paste'
  337. ),
  338. ), array(
  339. 'conditions' => array('steps' => array('create'), 'traffic/limit' => 0, 'main/burnafterreadingselected' => true),
  340. 'settings' => array('sleep(3)'),
  341. 'type' => 'True',
  342. 'args' => array(
  343. '$this->_model->exists($response["id"])',
  344. 'when sizelimit limit is not reached, paste exists after posting data'
  345. ),
  346. ),
  347. ),
  348. 'affects' => array('create')
  349. ),
  350. ),
  351. 'traffic/limit' => array(
  352. array(
  353. 'setting' => 0,
  354. 'tests' => array(
  355. array(
  356. 'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
  357. 'type' => 'Equals',
  358. 'args' => array(
  359. 0,
  360. '$response["status"]',
  361. 'when traffic limit is disabled, successfully create paste'
  362. ),
  363. ), array(
  364. 'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
  365. 'type' => 'True',
  366. 'args' => array(
  367. '$this->_model->exists($response["id"])',
  368. 'when traffic limit is disabled, paste exists after posting data'
  369. ),
  370. ),
  371. ),
  372. 'affects' => array('create')
  373. ), array(
  374. 'setting' => 10,
  375. 'tests' => array(
  376. array(
  377. 'conditions' => array('steps' => array('create')),
  378. 'type' => 'Equals',
  379. 'args' => array(
  380. 1,
  381. '$response["status"]',
  382. 'when traffic limit is on and we do not wait, fail to create paste'
  383. ),
  384. ),
  385. ),
  386. 'affects' => array('create')
  387. ), array(
  388. 'setting' => 2,
  389. 'tests' => array(
  390. array(
  391. 'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
  392. 'settings' => array('sleep(3)'),
  393. 'type' => 'Equals',
  394. 'args' => array(
  395. 0,
  396. '$response["status"]',
  397. 'when traffic limit is on and we wait, successfully create paste'
  398. ),
  399. ), array(
  400. 'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
  401. 'settings' => array('sleep(3)'),
  402. 'type' => 'True',
  403. 'args' => array(
  404. '$this->_model->exists($response["id"])',
  405. 'when traffic limit is on and we wait, paste exists after posting data'
  406. ),
  407. ),
  408. ),
  409. 'affects' => array('create')
  410. ),
  411. ),
  412. ));
  413. class configurationTestGenerator
  414. {
  415. /**
  416. * endless loop protection, since we're working with a recursive function,
  417. * creating factorial configurations
  418. * @var int
  419. */
  420. const MAX_ITERATIONS = 1000;
  421. /**
  422. * options to test
  423. * @var array
  424. */
  425. private $_options;
  426. /**
  427. * iteration count to guarantee timely end
  428. * @var int
  429. */
  430. private $_iterationCount = 0;
  431. /**
  432. * generated configurations
  433. * @var array
  434. */
  435. private $_configurations = array(
  436. array('options' => array(), 'tests' => array(), 'affects' => array())
  437. );
  438. /**
  439. * constructor, generates the configuration test
  440. * @param array $options
  441. */
  442. public function __construct($options) {
  443. $this->_options = $options;
  444. // generate all possible combinations of options: options^settings
  445. $this->_generateConfigurations();
  446. $this->_writeConfigurationTest();
  447. }
  448. /**
  449. * write configuration test file based on generated configuration array
  450. */
  451. private function _writeConfigurationTest()
  452. {
  453. $defaultOptions = parse_ini_file(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', true);
  454. $code = $this->_getHeader();
  455. foreach ($this->_configurations as $key => $conf) {
  456. $fullOptions = array_replace_recursive($defaultOptions, $conf['options']);
  457. $options = helper::var_export_min($fullOptions, true);
  458. foreach ($conf['affects'] as $step) {
  459. $testCode = $preCode = array();
  460. foreach ($conf['tests'] as $tests) {
  461. foreach ($tests[0] as $test) {
  462. // skip if test does not affect this step
  463. if (!in_array($step, $tests[1])) {
  464. continue;
  465. }
  466. // skip if not all test conditions are met
  467. if (array_key_exists('conditions', $test)) {
  468. while (list($path, $setting) = each($test['conditions'])) {
  469. if ($path == 'steps' && !in_array($step, $setting)) {
  470. continue 2;
  471. } elseif($path != 'steps') {
  472. list($section, $option) = explode('/', $path);
  473. if ($fullOptions[$section][$option] !== $setting) {
  474. continue 2;
  475. }
  476. }
  477. }
  478. }
  479. if (array_key_exists('settings', $test)) {
  480. foreach ($test['settings'] as $setting) {
  481. $preCode[$setting] = $setting;
  482. }
  483. }
  484. $args = array();
  485. foreach ($test['args'] as $arg) {
  486. if (is_string($arg) && strpos($arg, '$') === 0) {
  487. $args[] = $arg;
  488. } else {
  489. $args[] = helper::var_export_min($arg, true);
  490. }
  491. }
  492. $testCode[] = array($test['type'], implode(', ', $args));
  493. }
  494. }
  495. $code .= $this->_getFunction(
  496. ucfirst($step), $key, $options, $preCode, $testCode
  497. );
  498. }
  499. }
  500. $code .= '}' . PHP_EOL;
  501. file_put_contents('configuration.php', $code);
  502. }
  503. /**
  504. * get header of configuration test file
  505. *
  506. * @return string
  507. */
  508. private function _getHeader()
  509. {
  510. return <<<'EOT'
  511. <?php
  512. /**
  513. * DO NOT EDIT: This file is automatically generated by configGenerator.php
  514. */
  515. class configurationTest extends PHPUnit_Framework_TestCase
  516. {
  517. private static $pasteid = '5e9bc25c89fb3bf9';
  518. private static $paste = array(
  519. 'data' => '{"iv":"EN39/wd5Nk8HAiSG2K5AsQ","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"QKN1DBXe5PI","ct":"8hA83xDdXjD7K2qfmw5NdA"}',
  520. 'meta' => array(
  521. 'postdate' => 1344803344,
  522. 'opendiscussion' => true,
  523. ),
  524. );
  525. private $_model;
  526. private $_conf;
  527. public function setUp()
  528. {
  529. /* Setup Routine */
  530. $this->_conf = PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini';
  531. if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
  532. rename($this->_conf, $this->_conf . '.bak');
  533. $this->_model = zerobin_data::getInstance(array('dir' => PATH . 'data'));
  534. serversalt::setPath(PATH . 'data');
  535. $this->reset();
  536. }
  537. public function tearDown()
  538. {
  539. /* Tear Down Routine */
  540. rename($this->_conf . '.bak', $this->_conf);
  541. }
  542. public function reset($configuration = array())
  543. {
  544. $_POST = array();
  545. $_GET = array();
  546. $_SERVER = array();
  547. if ($this->_model->exists(self::$pasteid))
  548. $this->_model->delete(self::$pasteid);
  549. helper::createIniFile($this->_conf, $configuration);
  550. }
  551. EOT;
  552. }
  553. /**
  554. * get unit tests function block
  555. *
  556. * @param string $step
  557. * @param int $key
  558. * @param array $options
  559. * @param array $preCode
  560. * @param array $testCode
  561. * @return string
  562. */
  563. private function _getFunction($step, $key, &$options, $preCode, $testCode)
  564. {
  565. if (count($testCode) == 0) {
  566. echo "skipping creation of test$step$key, no valid tests found for configuration: $options". PHP_EOL;
  567. return '';
  568. }
  569. $preString = $testString = '';
  570. foreach ($preCode as $setting) {
  571. $preString .= " $setting;" . PHP_EOL;
  572. }
  573. foreach ($testCode as $test) {
  574. $type = $test[0];
  575. $args = $test[1];
  576. $testString .= " \$this->assert$type($args);" . PHP_EOL;
  577. }
  578. $code = <<<EOT
  579. /**
  580. * @runInSeparateProcess
  581. */
  582. public function test$step$key()
  583. {
  584. \$this->reset($options);
  585. EOT;
  586. // step specific initialization
  587. switch ($step) {
  588. case 'Create':
  589. $code .= PHP_EOL . <<<'EOT'
  590. $_POST = self::$paste;
  591. $_SERVER['REMOTE_ADDR'] = '::1';
  592. EOT;
  593. break;
  594. case 'Read':
  595. $code .= PHP_EOL . <<<'EOT'
  596. $this->_model->create(self::$pasteid, self::$paste);
  597. $_SERVER['QUERY_STRING'] = self::$pasteid;
  598. EOT;
  599. break;
  600. case 'Delete':
  601. $code .= PHP_EOL . <<<'EOT'
  602. $this->_model->create(self::$pasteid, self::$paste);
  603. $this->assertTrue($this->_model->exists(self::$pasteid), 'paste exists before deleting data');
  604. $_GET['pasteid'] = self::$pasteid;
  605. $_GET['deletetoken'] = hash_hmac('sha1', self::$pasteid, serversalt::get());
  606. EOT;
  607. break;
  608. }
  609. // all steps
  610. $code .= PHP_EOL . $preString;
  611. $code .= <<<'EOT'
  612. ob_start();
  613. new zerobin;
  614. $content = ob_get_contents();
  615. EOT;
  616. // step specific tests
  617. switch ($step) {
  618. case 'Create':
  619. $code .= <<<'EOT'
  620. $response = json_decode($content, true);
  621. EOT;
  622. break;
  623. case 'Read':
  624. $code .= <<<'EOT'
  625. $this->assertTag(
  626. array(
  627. 'id' => 'cipherdata',
  628. 'content' => htmlspecialchars(json_encode(self::$paste), ENT_NOQUOTES)
  629. ),
  630. $content,
  631. 'outputs data correctly'
  632. );
  633. EOT;
  634. break;
  635. case 'Delete':
  636. $code .= <<<'EOT'
  637. $this->assertTag(
  638. array(
  639. 'id' => 'status',
  640. 'content' => 'Paste was properly deleted'
  641. ),
  642. $content,
  643. 'outputs deleted status correctly'
  644. );
  645. $this->assertFalse($this->_model->exists(self::$pasteid), 'paste successfully deleted');
  646. EOT;
  647. break;
  648. }
  649. return $code . PHP_EOL . PHP_EOL . $testString . ' }' . PHP_EOL . PHP_EOL;
  650. }
  651. /**
  652. * recursive function to generate configurations based on options
  653. *
  654. * @throws Exception
  655. * @return array
  656. */
  657. private function _generateConfigurations()
  658. {
  659. // recursive factorial function
  660. if (++$this->_iterationCount > self::MAX_ITERATIONS) {
  661. echo 'max iterations reached, stopping', PHP_EOL;
  662. return $this->_configurations;
  663. }
  664. echo "generateConfigurations: iteration $this->_iterationCount", PHP_EOL;
  665. $continue = list($path, $settings) = each($this->_options);
  666. if ($continue === false) {
  667. return $this->_configurations;
  668. }
  669. list($section, $option) = explode('/', $path);
  670. for ($c = 0, $max = count($this->_configurations); $c < $max; ++$c) {
  671. if (!array_key_exists($section, $this->_configurations[$c]['options'])) {
  672. $this->_configurations[$c]['options'][$section] = array();
  673. }
  674. if (count($settings) == 0) {
  675. throw new Exception("Check your \$options: option $option has no settings!");
  676. }
  677. // set the first setting in the original configuration
  678. $setting = current($settings);
  679. $this->_addSetting($this->_configurations[$c], $setting, $section, $option);
  680. // create clones for each of the other settings
  681. while ($setting = next($settings)) {
  682. $clone = $this->_configurations[$c];
  683. $this->_configurations[] = $this->_addSetting($clone, $setting, $section, $option);
  684. }
  685. reset($settings);
  686. }
  687. return $this->_generateConfigurations();
  688. }
  689. /**
  690. * add a setting to the given configuration
  691. *
  692. * @param array $configuration
  693. * @param array $setting
  694. * @param string $section
  695. * @param string $option
  696. * @throws Exception
  697. * @return array
  698. */
  699. private function _addSetting(&$configuration, &$setting, &$section, &$option) {
  700. if (++$this->_iterationCount > self::MAX_ITERATIONS) {
  701. echo 'max iterations reached, stopping', PHP_EOL;
  702. return $configuration;
  703. }
  704. echo "addSetting: iteration $this->_iterationCount", PHP_EOL;
  705. if (
  706. array_key_exists($option, $configuration['options'][$section]) &&
  707. $configuration['options'][$section][$option] === $setting['setting']
  708. ) {
  709. $val = helper::var_export_min($setting['setting'], true);
  710. throw new Exception("Endless loop or error in options detected: option '$option' already exists with setting '$val' in one of the configurations!");
  711. }
  712. $configuration['options'][$section][$option] = $setting['setting'];
  713. $configuration['tests'][$option] = array($setting['tests'], $setting['affects']);
  714. foreach ($setting['affects'] as $affects) {
  715. if (!in_array($affects, $configuration['affects'])) {
  716. $configuration['affects'][] = $affects;
  717. }
  718. }
  719. return $configuration;
  720. }
  721. }