configGenerator.php 28 KB

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