diff --git a/wp_config_snapshots/PluginSnapshot.php b/wp_config_snapshots/PluginSnapshot.php new file mode 100644 index 0000000..31fdf2b --- /dev/null +++ b/wp_config_snapshots/PluginSnapshot.php @@ -0,0 +1,35 @@ +current_version($wp_version); + $vco->supported_version('3.4.1'); + $vco->deprecated_message( + 'Notice: The Plugin Snapshot framework is only supported to WP version 3.4.1. Please check for updates.'); + $vco->set_block_ui(false); + return $vco; + } +} \ No newline at end of file diff --git a/wp_config_snapshots/Stamp.php b/wp_config_snapshots/Stamp.php new file mode 100644 index 0000000..63869d9 --- /dev/null +++ b/wp_config_snapshots/Stamp.php @@ -0,0 +1,388 @@ +tpl=$templ; + $this->id = $id; + if ($autofind) + $this->autoFindRegions(); + } + + protected $snippets = array(); + + + + /** + * Loads a template and returns an instance configured + * with this template; supports caching. + * + * @param string $filename template file path + * + * @return Stamp $instance instance of Stamp class + */ + public static function load($filename) { + + $hash = md5($filename); + if (isset(self::$tcache[$hash])) return self::$tcache[$hash]; + + if (!file_exists($filename)) { + throw new Exception("Could not find file $filename"); + } + if (!is_readable($filename)) { + throw new Exception("File $filename is not readable"); + } + + $str = file_get_contents($filename); + self::$tcache[ $hash ] = $str; + + return new self( $str ); + + } + + /** + * Finds a region in the template marked by the comment markers + * this area will be selected + * + * This method returns a Region Struct: + * + * array( + * "begin" => $begin -- begin of region (string position), + * "padBegin" => $padBegin -- begin of actual inner HTML (without the begin marker itself) + * "end" => $end -- end of region before end marker + * "padEnd" => -- end of region after end marker + * "copy" => -- inner HTML between markers + * + * ); + * + * @param string $id marker ID + * @param integer $next_pos starting position from which to search + * + * @return array $struct + */ + public function find($id, $nextPos = 0) { + $cacheKey = $id.'|'.$nextPos; + if (isset($this->fcache[$cacheKey])) { + return $this->fcache[$cacheKey]; + } + if($nextPos >= strlen($this->tpl)) return array(); + $fid = ""; + $fidEnd = ""; + $len = strlen($fid); + $begin = strpos($this->tpl, $fid, $nextPos); + $padBegin = $begin + $len; + $rest = substr($this->tpl, $padBegin); + $end = strpos($rest, $fidEnd); + $padEnd = $end + strlen($fidEnd); + $stamp = substr($rest, 0, $end); + $keys = array( "begin"=>$begin, "padBegin"=>$padBegin, "end"=>$end, "padEnd"=>$padEnd, "copy"=>$stamp ); + $this->fcache[$cacheKey] = $keys; + return $keys; + } + + + /** + * Automatically scans template and cuts out all regions prefixed + * with "cut:" + * + * @return void + */ + public function autoFindRegions() { + + + while((($pos = strpos($this->tpl,'',$pos); + $id = trim(substr($this->tpl,$pos+9,$end-($pos+9))); + $this->snippets[$id]=$this->cut($id); + $this->snippets[$id]->id = $id; + + } + + + $pos=0; + while((strpos($this->tpl,'',$pos); + $end2 = strpos($this->tpl,'(',$pos); + $sid = trim(substr($this->tpl,$pos+11,(min($end,$end2)-($pos+11)))); + if ($pos!==false) { + //found slot + $end = strpos($this->tpl,'-->',$pos); + $slot = substr($this->tpl,$pos,$end-$pos); + if (($respos=strpos($slot,'('))!==false) { + //has slot restriction from designer + $restrictions = substr($slot,$respos+1,strpos($slot,')')-($respos+1)); + $resIds = explode(',',$restrictions); + $resIdArray = array(); + foreach($resIds as $k=>$v) $resIdArray[$v]=true; + $this->restrictions[ $sid ] = $resIdArray; + } + } + $pos = $end; + + } + + + } + + /** + * Cuts a region from the template. + * + * @param string $id ID of the region that has to be cut + * + * @return Stamp $stamp instance of Stamp + */ + public function cut($id) { + $snippet = $this->copy('cut:'.$id); + $this->replace("cut:$id",""); + return $snippet; + } + + /** + * Returns a new Stamp Template, containing a copy of the + * specified region ID. + * + * @param string $id region id + * + * @return Stamp $stamp the new Stamp instance + */ + public function copy($id) { + $snip = $this->find($id); + return new Stamp( $snip["copy"] ); + } + + /** + * Cleans the contents of the current stamp + * + * @return Stamp + */ + public function clean() { + return $this->paste(""); + } + + /** + * Replace a region specified by $where region ID with string $paste. + * + * @param string $where region ID + * @param string $paste replacement string + * + * @return Stamp stamp + */ + public function replace( $where, $paste ) { + $nextPos = 0; + while($nextPos < strlen($this->tpl)) { + $keys = $this->find($where, $nextPos); + if(!$keys['begin']) break; + $nextPos = $keys['begin'] + strlen($paste); + $suffix = substr($this->tpl,$keys["padEnd"]+$keys["padBegin"]); + $prefix = substr($this->tpl,0,$keys["begin"]); + $copy = $prefix.$paste.$suffix; + $this->tpl = $copy; + } + return $this; + } + + + /** + * Puts $text in slot Slot ID, marker #slot# will be replaced. + * + * @param string $slotID slot identifier + * @param string $text + * + * @return Stamp + */ + public function put($slotID, $text) { + $text = htmlentities($text); + $slotFormat = "#$slotID#"; + $this->tpl = str_replace( $slotFormat, htmlspecialchars( $text ), $this->tpl); + return $this; + } + + /** + * Checks if the template contains slot Slot ID. + * + * @param string $slotID slot ID + * + * @return bool $containsSlot result of check + */ + public function hasSimpleSlot($slotID) { + if (strpos($this->tpl,"#".$slotID."#")!==false) return true; else return false; + } + + + /** + * Pastes the contents of string $paste after the first '>' + * + * @param $paste string HTML + * + * @return Stamp $chainable Chainable + */ + public function pastePad($paste) { + $beginOfTag = strpos($this->tpl,">"); + $endOfTag = strrpos($this->tpl,"<"); + $this->tpl = substr($this->tpl, 0, $beginOfTag+1).$paste.substr($this->tpl,$endOfTag); + return $this; + } + + + /** + * Fetches a previously cut snippet by id. + * + * @param string $id ID of the snippet you want to fetch. + * + * @return Stamp $stamp instance of Stamp + */ + public function fetch($id) { + $new = new self( $this->snippets[$id], $this->snippets[$id]->id); + $new->snippets = $this->snippets[$id]->snippets; + return $new; + } + + + /** + * Pastes a snippet in a region. + * + * @param string $sid Region ID + * @param string $snippet HTML Snippet to drop in the region + * + * @return void + */ + public function pasteIn($sid,$snippet) { + $id = $snippet->id; + if ( (isset($this->restrictions[$sid]) && + isset($this->restrictions[$sid][$id])) || + (!isset($this->restrictions[$sid])) + ) { + $slotPos = strpos($this->tpl,"#', $this->tpl, + $markersGroups); + $markersList = array(); + foreach($markersGroups[1] as $marker) { + if(strstr($this->tpl, "")) { + $markersList []= $marker; + } + } + return $markersList; + } + + /** + * Replaces all markers in current template with child's ones + * (like reverse (to make it chainable) of Django's {% extends %}) + * + * @return Stamp $chainable Chainable + */ + public function extendWith($childString) { + $child = new Stamp($childString); + $parent = $this; + foreach($child->getMarkersList() as $marker) { + $copyInChild = $child->copy($marker); + $parent->replace($marker, $copyInChild); + } + return $this; + } + + /** + * Removes all comments and redundant whitespaces from template. + * + * @return Stamp $self + */ + public function wash() { + $this->tpl = preg_replace("//m","",$this->tpl); + $this->tpl = preg_replace("/\n[\n\t\s]*\n/m","\n",$this->tpl); + $this->tpl = trim($this->tpl); + return $this; + } + +} + + + + + + + + + + + + + + + + + diff --git a/wp_config_snapshots/js/snapshots.js b/wp_config_snapshots/js/snapshots.js new file mode 100644 index 0000000..bc7fd11 --- /dev/null +++ b/wp_config_snapshots/js/snapshots.js @@ -0,0 +1,28 @@ +(function($, exports, undefined) { + $(document).ready(function() { + + $('.restore_snapshot').click(function() { + var snapshot = $(this).parent().find('select').val(), type = $(this).parent().parent().data('type'); + console.log(snapshot, type); + $.post(ajaxurl, {action: 'restore_wp_config_snapshot', snapshot_type: type, snapshot_name: snapshot}, function(r) { + var res = $.parseJSON(r); + if (res.status !== 'success' && res.message) return alert(res.message); + return window.location.reload(); + }); + }); + + $('.take_snapshot').click(function() { + var $el = $(this), snapshot = $(this).parent().find('.new_snapshot_name').val(), type = $(this).parent().parent().data('type'); + if (snapshot.trim() === '') return alert('Please enter a name for the new snapshot.'); + $(this).parent().find('.new_snapshot_name').val(''); + $.post(ajaxurl, {action: 'add_wp_config_snapshot', snapshot_type: type, snapshot_name: snapshot}, function(r) { + var res = $.parseJSON(r); + if (res.status !== 'success' && res.message) return alert(res.message); + $el.parent().parent().find('.wp_settings_snapshot_select') + .append('').val(snapshot); + return 1; + }); + return 1; + }); + }); +}(jQuery, window)) \ No newline at end of file diff --git a/wp_config_snapshots/plugin_snapshots.php b/wp_config_snapshots/plugin_snapshots.php new file mode 100644 index 0000000..a1c8777 --- /dev/null +++ b/wp_config_snapshots/plugin_snapshots.php @@ -0,0 +1,24 @@ +register_snapshot_module($ps); + } +} + +add_action('config_snapshot_plugin_loaded', array('PluginSnapshotPlugin', 'initialize')); \ No newline at end of file diff --git a/wp_config_snapshots/tests/EnhanceTestFramework.php b/wp_config_snapshots/tests/EnhanceTestFramework.php new file mode 100644 index 0000000..fb7c91d --- /dev/null +++ b/wp_config_snapshots/tests/EnhanceTestFramework.php @@ -0,0 +1,1620 @@ +discoverTests($path, $isRecursive, $excludeRules); + } + + public static function runTests($output = TemplateType::Html) + { + self::setInstance(); + self::$Instance->runTests($output); + } + + public static function getCodeCoverageWrapper($className, $args = null) + { + self::setInstance(); + self::$Instance->registerForCodeCoverage($className); + return new CodeCoverageWrapper($className, $args); + } + + public static function log($className, $methodName) + { + self::setInstance(); + self::$Instance->log($className, $methodName); + } + + public static function getScenario($className, $args = null) + { + return new Scenario($className, $args, self::$Language); + } + + public static function setInstance() + { + if (self::$Instance === null) { + self::$Instance = new EnhanceTestFramework(self::$Language); + } + } +} + +// Public API +class TestFixture +{ + +} + +// Public API +class MockFactory +{ + public static function createMock($typeName) + { + return new Mock($typeName, true, Core::getLanguage()); + } +} + +// Public API +class StubFactory +{ + public static function createStub($typeName) + { + return new Mock($typeName, false, Core::getLanguage()); + } +} + +// Public API +class Expect +{ + const AnyValue = 'ENHANCE_ANY_VALUE_WILL_DO'; + + public static function method($methodName) + { + $expectation = new Expectation(Core::getLanguage()); + return $expectation->method($methodName); + } + + public static function getProperty($propertyName) + { + $expectation = new Expectation(Core::getLanguage()); + return $expectation->getProperty($propertyName); + } + + public static function setProperty($propertyName) + { + $expectation = new Expectation(Core::getLanguage()); + return $expectation->setProperty($propertyName); + } +} + +// Public API +class Assert +{ + /** @var Assertions $EnhanceAssertions */ + private static $EnhanceAssertions; + + private static function GetEnhanceAssertionsInstance() + { + if(self::$EnhanceAssertions === null) { + self::$EnhanceAssertions = new Assertions(Core::getLanguage()); + } + return self::$EnhanceAssertions; + } + + public static function areIdentical($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->areIdentical($expected, $actual); + } + + public static function areNotIdentical($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->areNotIdentical($expected, $actual); + } + + public static function isTrue($actual) + { + self::GetEnhanceAssertionsInstance()->isTrue($actual); + } + + public static function isFalse($actual) + { + self::GetEnhanceAssertionsInstance()->isFalse($actual); + } + + public static function isNull($actual) + { + self::GetEnhanceAssertionsInstance()->isNull($actual); + } + + public static function isNotNull($actual) + { + self::GetEnhanceAssertionsInstance()->isNotNull($actual); + } + + public static function isArray($actual) + { + self::GetEnhanceAssertionsInstance()->isArray($actual); + } + + public static function isNotArray($actual) + { + self::GetEnhanceAssertionsInstance()->isNotArray($actual); + } + + public static function isBool($actual) + { + self::GetEnhanceAssertionsInstance()->isBool($actual); + } + + public static function isNotBool($actual) + { + self::GetEnhanceAssertionsInstance()->isNotBool($actual); + } + + public static function isFloat($actual) + { + self::GetEnhanceAssertionsInstance()->isFloat($actual); + } + + public static function isNotFloat($actual) + { + self::GetEnhanceAssertionsInstance()->isNotFloat($actual); + } + + public static function isInt($actual) + { + self::GetEnhanceAssertionsInstance()->isInt($actual); + } + + public static function isNotInt($actual) + { + self::GetEnhanceAssertionsInstance()->isNotInt($actual); + } + + public static function isNumeric($actual) + { + self::GetEnhanceAssertionsInstance()->isNumeric($actual); + } + + public static function isNotNumeric($actual) + { + self::GetEnhanceAssertionsInstance()->isNotNumeric($actual); + } + + public static function isObject($actual) + { + self::GetEnhanceAssertionsInstance()->isObject($actual); + } + + public static function isNotObject($actual) + { + self::GetEnhanceAssertionsInstance()->isNotObject($actual); + } + + public static function isResource($actual) + { + self::GetEnhanceAssertionsInstance()->isResource($actual); + } + + public static function isNotResource($actual) + { + self::GetEnhanceAssertionsInstance()->isNotResource($actual); + } + + public static function isScalar($actual) + { + self::GetEnhanceAssertionsInstance()->isScalar($actual); + } + + public static function isNotScalar($actual) + { + self::GetEnhanceAssertionsInstance()->isNotScalar($actual); + } + + public static function isString($actual) + { + self::GetEnhanceAssertionsInstance()->isString($actual); + } + + public static function isNotString($actual) + { + self::GetEnhanceAssertionsInstance()->isNotString($actual); + } + + public static function contains($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->contains($expected, $actual); + } + + public static function notContains($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->notContains($expected, $actual); + } + + public static function fail() + { + self::GetEnhanceAssertionsInstance()->fail(); + } + + public static function inconclusive() + { + self::GetEnhanceAssertionsInstance()->inconclusive(); + } + + public static function isInstanceOfType($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->isInstanceOfType($expected, $actual); + } + + public static function isNotInstanceOfType($expected, $actual) + { + self::GetEnhanceAssertionsInstance()->isNotInstanceOfType($expected, $actual); + } + + public static function throws($class, $methodName, $args = null) + { + self::GetEnhanceAssertionsInstance()->throws($class, $methodName, $args); + } +} + +// Internal Workings +// You don't need to call any of these bits directly - use the public API above, which will +// use the stuff below to carry out your tests! + +class TextFactory +{ + public static $Text; + + public static function getLanguageText($language) + { + if (self::$Text === null) { + $languageClass = 'Enhance\Text' . $language; + self::$Text = new $languageClass(); + } + return self::$Text; + } +} + +class TextEn +{ + public $FormatForTestRunTook = 'Test run took {0} seconds'; + public $FormatForExpectedButWas = 'Expected {0} but was {1}'; + public $FormatForExpectedNotButWas = 'Expected NOT {0} but was {1}'; + public $FormatForExpectedContainsButWas = 'Expected to contain {0} but was {1}'; + public $FormatForExpectedNotContainsButWas = 'Expected NOT to contain {0} but was {1}'; + public $EnhanceTestFramework = 'Enhance Test Framework'; + public $EnhanceTestFrameworkFull = 'Enhance PHP Unit Testing Framework'; + public $TestResults = 'Test Results'; + public $Test = 'Test'; + public $TestPassed = 'Test Passed'; + public $TestFailed = 'Test Failed'; + public $Passed = 'Passed'; + public $Failed = 'Failed'; + public $ExpectationFailed = 'Expectation failed'; + public $Expected = 'Expected'; + public $Called = 'Called'; + public $InconclusiveOrNotImplemented = 'Inconclusive or not implemented'; + public $Times = 'Times'; + public $MethodCoverage = 'Method Coverage'; + public $Copyright = 'Copyright'; + public $ExpectedExceptionNotThrown = 'Expected exception was not thrown'; + public $CannotCallVerifyOnStub = 'Cannot call VerifyExpectations on a stub'; + public $ReturnsOrThrowsNotBoth = 'You must only set a single return value (1 returns() or 1 throws())'; + public $ScenarioWithExpectMismatch = 'Scenario must be initialised with the same number of "with" and "expect" calls'; + public $LineFile = 'Line {0} in file {1}'; +} + +class TextDe +{ + public $FormatForTestRunTook = 'Fertig nach {0} Sekunden'; + public $FormatForExpectedButWas = 'Erwartet {0} aber war {1}'; + public $FormatForExpectedNotButWas = 'Erwartet nicht {0} aber war {1}'; + public $FormatForExpectedContainsButWas = 'Erwartet {0} zu enthalten, aber war {1}'; + public $FormatForExpectedNotContainsButWas = 'Erwartet {0} nicht zu enthalten, aber war {1}'; + public $EnhanceTestFramework = 'Enhance Test-Rahmen'; + public $EnhanceTestFrameworkFull = 'Maßeinheits-Prüfungs-Rahmen'; + public $TestResults = 'Testergebnisse'; + public $Test = 'Test'; + public $TestPassed = 'Test bestehen'; + public $TestFailed = 'Test durchfallen'; + public $Passed = 'Bestehen'; + public $Failed = 'Durchfallen'; + public $ExpectationFailed = 'Erwartung durchfallen'; + public $Expected = 'Erwartet'; + public $Called = 'Benannt'; + public $InconclusiveOrNotImplemented = 'Unbestimmt oder nicht durchgefuert'; + public $Times = 'Ereignisse'; + public $MethodCoverage = 'Methodenbehandlung'; + public $Copyright = 'Copyright'; + public $ExpectedExceptionNotThrown = 'Die Ausnahme wird nicht ausgeloest'; + public $CannotCallVerifyOnStub = 'Kann VerifyExpectations auf einem Stub nicht aufrufen'; + public $ReturnsOrThrowsNotBoth = 'Sie müssen einen einzelnen Return Value nur einstellen'; + public $ScenarioWithExpectMismatch = 'Szenarium muss mit der gleichen Zahl von "With" und "Expect" calls initialisiert werden'; + public $LineFile = 'Zeile {0} der Datei {1}'; +} + +class EnhanceTestFramework +{ + private $FileSystem; + private $Text; + private $Tests = array(); + private $Results = array(); + private $Errors = array(); + private $Duration; + private $MethodCalls = array(); + private $Language; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + $this->FileSystem = new FileSystem(); + $this->Language = $language; + } + + public function discoverTests($path, $isRecursive, $excludeRules) + { + $directory = rtrim($path, '/'); + if (is_dir($directory)) { + $phpFiles = $this->FileSystem->getFilesFromDirectory($directory, $isRecursive, $excludeRules); + foreach ($phpFiles as $file) { + /** @noinspection PhpIncludeInspection */ + include_once($file); + } + } + } + + public function runTests($output) + { + $this->getTestFixturesByParent(); + $this->run(); + + if(PHP_SAPI === 'cli' && $output != TemplateType::Tap) { + $output = TemplateType::Cli; + } + + $OutputTemplate = TemplateFactory::createOutputTemplate($output, $this->Language); + echo $OutputTemplate->get( + $this->Errors, + $this->Results, + $this->Text, + $this->Duration, + $this->MethodCalls + ); + + if (count($this->Errors) > 0) { + exit(1); + } else { + exit(0); + } + } + + public function log($className, $methodName) + { + $index = $this->getMethodIndex($className, $methodName); + if (array_key_exists($index ,$this->MethodCalls)) { + $this->MethodCalls[$index] = $this->MethodCalls[$index] + 1; + } + } + + public function registerForCodeCoverage($className) + { + $classMethods = get_class_methods($className); + foreach($classMethods as $methodName) { + $index = $this->getMethodIndex($className, $methodName); + if (!array_key_exists($index ,$this->MethodCalls)) { + $this->MethodCalls[$index] = 0; + } + } + } + + private function getMethodIndex($className, $methodName) + { + return $className . '#' . $methodName; + } + + private function getTestFixturesByParent() + { + $classes = get_declared_classes(); + foreach($classes as $className) { + $this->AddClassIfTest($className); + } + } + + private function AddClassIfTest($className) + { + $parentClassName = get_parent_class($className); + if ($parentClassName === 'Enhance\TestFixture') { + $instance = new $className(); + $this->addFixture($instance); + } else { + $ancestorClassName = get_parent_class($parentClassName); + if ($ancestorClassName === 'Enhance\TestFixture') { + $instance = new $className(); + $this->addFixture($instance); + } + } + } + + private function addFixture($class) + { + $classMethods = get_class_methods($class); + foreach($classMethods as $method) { + if (strtolower($method) !== 'setup' && strtolower($method) !== 'teardown') { + $reflection = new \ReflectionMethod($class, $method); + if ($reflection->isPublic()) { + $this->addTest($class, $method); + } + } + } + } + + private function addTest($class, $method) + { + $testMethod = new Test($class, $method); + $this->Tests[] = $testMethod; + } + + private function run() + { + $start = time(); + foreach($this->Tests as /** @var Test $test */ $test) { + $result = $test->run(); + if ($result) { + $message = $test->getTestName() . ' - ' . $this->Text->Passed; + $this->Results[] = new TestMessage($message, $test, true); + } else { + $message = '['. str_replace('{0}', $test->getLine(), str_replace('{1}', $test->getFile(), $this->Text->LineFile)) . '] ' . + $test->getTestName() . ' - ' . + $this->Text->Failed . ' - ' . $test->getMessage(); + $this->Errors[] = new TestMessage($message, $test, false); + } + } + $this->Duration = time() - $start; + } +} + +class FileSystem +{ + public function getFilesFromDirectory($directory, $isRecursive, $excludeRules) + { + $files = array(); + if ($handle = opendir($directory)) { + while (false !== ($file = readdir($handle))) { + if ($file != '.' && $file != '..' && strpos($file, '.') !== 0) { + if ($this->isFolderExcluded($file, $excludeRules)){ + continue; + } + + if(is_dir($directory . '/' . $file)) { + if ($isRecursive) { + $dir2 = $directory . '/' . $file; + $files[] = $this->getFilesFromDirectory($dir2, $isRecursive, $excludeRules); + } + } else { + $files[] = $directory . '/' . $file; + } + } + } + closedir($handle); + } + return $this->flattenArray($files); + } + + private function isFolderExcluded($folder, $excludeRules) + { + $folder = substr($folder, strrpos($folder, '/')); + + foreach ($excludeRules as $excluded){ + if ($folder === $excluded){ + return true; + } + } + return false; + } + + public function flattenArray($array) + { + $merged = array(); + foreach($array as $a) { + if(is_array($a)) { + $merged = array_merge($merged, $this->flattenArray($a)); + } else { + $merged[] = $a; + } + } + return $merged; + } +} + +class TestMessage +{ + public $Message; + public $Test; + public $IsPass; + + public function __construct($message, $test, $isPass) + { + $this->Message = $message; + $this->Test = $test; + $this->IsPass = $isPass; + } +} + +class Test +{ + private $ClassName; + private $TestName; + private $TestMethod; + private $SetUpMethod; + private $TearDownMethod; + private $Message; + private $Line; + private $File; + + public function __construct($class, $method) + { + $className = get_class($class); + $this->ClassName = $className; + $this->TestMethod = array($className, $method); + $this->SetUpMethod = array($className, 'setUp'); + $this->TearDownMethod = array($className, 'tearDown'); + $this->TestName = $method; + } + + public function getTestName() + { + return $this->TestName; + } + + public function getClassName() + { + return $this->ClassName; + } + + public function getMessage() + { + return $this->Message; + } + + public function getLine() + { + return $this->Line; + } + + public function getFile() + { + return $this->File; + } + + public function run() + { + /** @var $testClass iTestable */ + $testClass = new $this->ClassName(); + + try { + if (is_callable($this->SetUpMethod)) { + $testClass->setUp(); + } + } catch (\Exception $e) { } + + try { + $testClass->{$this->TestName}(); + $result = true; + } catch (TestException $e) { + $this->Message = $e->getMessage(); + $this->Line = $e->getLine(); + $this->File = pathinfo($e->getFile(), PATHINFO_BASENAME); + $result = false; + } + + try { + if (is_callable($this->TearDownMethod)) { + $testClass->tearDown(); + } + } catch (\Exception $e) { } + + return $result; + } +} + +class CodeCoverageWrapper +{ + private $Instance; + private $ClassName; + + public function __construct($className, $args) + { + $this->ClassName = $className; + if ($args !== null) { + $rc = new \ReflectionClass($className); + $this->Instance = $rc->newInstanceArgs($args); + } else { + $this->Instance = new $className(); + } + Core::log($this->ClassName, $className); + Core::log($this->ClassName, '__construct'); + } + + public function __call($methodName, $args = null) + { + Core::log($this->ClassName, $methodName); + if ($args !== null) { + /** @noinspection PhpParamsInspection */ + return call_user_func_array(array($this->Instance, $methodName), $args); + } else { + return $this->Instance->{$methodName}(); + } + } + + public function __get($propertyName) + { + return $this->Instance->{$propertyName}; + } + + public function __set($propertyName, $value) + { + $this->Instance->{$propertyName} = $value; + } +} + +class Mock +{ + private $IsMock; + private $Text; + private $ClassName; + private $Expectations = array(); + + public function __construct($className, $isMock, $language) + { + $this->IsMock = $isMock; + $this->ClassName = $className; + $this->Text = TextFactory::getLanguageText($language); + } + + public function addExpectation($expectation) + { + $this->Expectations[] = $expectation; + } + + public function verifyExpectations() + { + if (!$this->IsMock) { + throw new \Exception( + $this->ClassName . ': ' . $this->Text->CannotCallVerifyOnStub + ); + } + + foreach ($this->Expectations as /** @var Expectation $expectation */ $expectation) { + if (!$expectation->verify()) { + $Arguments = ''; + if (isset($expectation->MethodArguments)) { + foreach($expectation->MethodArguments as $argument) { + if (isset($Arguments[0])) { + $Arguments .= ', '; + } + $Arguments .= $argument; + } + } + + throw new \Exception( + $this->Text->ExpectationFailed . ' ' . + $this->ClassName . '->' . $expectation->MethodName . '(' . $Arguments . ') ' . + $this->Text->Expected . ' #' . $expectation->ExpectedCalls . ' ' . + $this->Text->Called . ' #' . $expectation->ActualCalls, 0); + } + } + } + + public function __call($methodName, $args) + { + return $this->getReturnValue('method', $methodName, $args); + } + + public function __get($propertyName) + { + return $this->getReturnValue('getProperty', $propertyName, array()); + } + + public function __set($propertyName, $value) + { + $this->getReturnValue('setProperty', $propertyName, array($value)); + } + + private function getReturnValue($type, $methodName, $args) + { + $Expectation = $this->getMatchingExpectation($type, $methodName, $args); + $Expected = true; + if ($Expectation === null) { + $Expected = false; + } + + if ($Expected) { + ++$Expectation->ActualCalls; + if ($Expectation->ReturnException) { + throw new \Exception($Expectation->ReturnValue); + } + return $Expectation->ReturnValue; + } + + if ($this->IsMock) { + throw new \Exception( + $this->Text->ExpectationFailed . ' ' . + $this->ClassName . '->' . $methodName . '(' . $args . ') ' . + $this->Text->Expected . ' #0 ' . + $this->Text->Called . ' #1', 0); + } + return null; + } + + private function getMatchingExpectation($type, $methodName, $arguments) + { + foreach ($this->Expectations as $expectation) { + if ($expectation->Type === $type) { + if ($expectation->MethodName === $methodName) { + $isMatch = true; + if ($expectation->ExpectArguments) { + $isMatch = $this->argumentsMatch( + $expectation->MethodArguments, + $arguments + ); + } + if ($isMatch) { + return $expectation; + } + } + } + } + return null; + } + + private function argumentsMatch($arguments1, $arguments2) + { + $Count1 = count($arguments1); + $Count2 = count($arguments2); + $isMatch = true; + if ($Count1 === $Count2) { + for ($i = 0; $i < $Count1; ++$i) { + if ($arguments1[$i] === Expect::AnyValue + || $arguments2[$i] === Expect::AnyValue) { + // No need to match + } else { + if ($arguments1[$i] !== $arguments2[$i]) { + $isMatch = false; + } + } + } + } else { + $isMatch = false; + } + return $isMatch; + } +} + +class Scenario +{ + private $Text; + private $Class; + private $FunctionName; + private $Inputs = array(); + private $Expectations = array(); + + public function __construct($class, $functionName, $language) + { + $this->Class = $class; + $this->FunctionName = $functionName; + $this->Text = TextFactory::getLanguageText($language); + } + + public function with() + { + $this->Inputs[] = func_get_args(); + return $this; + } + + public function expect() + { + $this->Expectations[] = func_get_args(); + return $this; + } + + public function verifyExpectations() + { + if (count($this->Inputs) !== count($this->Expectations)) { + throw new \Exception($this->Text->ScenarioWithExpectMismatch); + } + + $exceptionText = ''; + + while(count($this->Inputs) > 0) { + $input = array_shift($this->Inputs); + $expected = array_shift($this->Expectations); + $expected = $expected[0]; + + $actual = call_user_func_array(array($this->Class, $this->FunctionName), $input); + + if (is_float($expected)) { + if ((string)$expected !== (string)$actual) { + $exceptionText .= str_replace('{0}', $expected, str_replace('{1}', $actual, $this->Text->FormatForExpectedButWas)); + } + } elseif ($expected != $actual) { + $exceptionText .= str_replace('{0}', $expected, str_replace('{1}', $actual, $this->Text->FormatForExpectedButWas)); + } + } + + if ($exceptionText !== ''){ + throw new \Exception($exceptionText, 0); + } + } +} + +class Expectation +{ + public $MethodName; + public $MethodArguments; + public $ReturnValue; + public $ReturnException; + public $ExpectedCalls; + public $ActualCalls; + public $ExpectArguments; + public $ExpectTimes; + public $Type; + public $Text; + + public function __construct($language) + { + $this->ExpectedCalls = -1; + $this->ActualCalls = 0; + $this->ExpectArguments = false; + $this->ExpectTimes = false; + $this->ReturnException = false; + $this->ReturnValue = null; + $textFactory = new TextFactory(); + $this->Text = $textFactory->getLanguageText($language); + } + + public function method($methodName) + { + $this->Type = 'method'; + $this->MethodName = $methodName; + return $this; + } + + public function getProperty($propertyName) + { + $this->Type = 'getProperty'; + $this->MethodName = $propertyName; + return $this; + } + + public function setProperty($propertyName) + { + $this->Type = 'setProperty'; + $this->MethodName = $propertyName; + return $this; + } + + public function with() + { + $this->ExpectArguments = true; + $this->MethodArguments = func_get_args(); + return $this; + } + + public function returns($returnValue) + { + if ($this->ReturnValue !== null) { + throw new \Exception($this->Text->ReturnsOrThrowsNotBoth); + } + $this->ReturnValue = $returnValue; + return $this; + } + + public function throws($errorMessage) + { + if ($this->ReturnValue !== null) { + throw new \Exception($this->Text->ReturnsOrThrowsNotBoth); + } + $this->ReturnValue = $errorMessage; + $this->ReturnException = true; + return $this; + } + + public function times($expectedCalls) + { + $this->ExpectTimes = true; + $this->ExpectedCalls = $expectedCalls; + return $this; + } + + public function verify() + { + $ExpectationMet = true; + if ($this->ExpectTimes) { + if ($this->ExpectedCalls !== $this->ActualCalls) { + $ExpectationMet = false; + } + } + return $ExpectationMet; + } +} + +class Assertions +{ + private $Text; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + } + + public function areIdentical($expected, $actual) + { + if (is_float($expected)) { + if ((string)$expected !== (string)$actual) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } elseif ($expected !== $actual) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function areNotIdentical($expected, $actual) + { + if (is_float($expected)) { + if ((string)$expected === (string)$actual) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } elseif ($expected === $actual) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isTrue($actual) + { + if ($actual !== true) { + throw new TestException(str_replace('{0}', 'true', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isFalse($actual) + { + if ($actual !== false) { + throw new TestException(str_replace('{0}', 'false', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function contains($expected, $actual) + { + $result = strpos($actual, $expected); + if ($result === false) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedContainsButWas)), 0); + } + } + + public function notContains($expected, $actual) + { + $result = strpos($actual, $expected); + if ($result !== false) { + throw new TestException(str_replace('{0}', $this->getDescription($expected), str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotContainsButWas)), 0); + } + } + + public function isNull($actual) + { + if ($actual !== null) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotNull($actual) + { + if ($actual === null) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isArray($actual) + { + if (!is_array($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotArray($actual) + { + if (is_array($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isBool($actual) + { + if (!is_bool($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotBool($actual) + { + if (is_bool($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isFloat($actual) + { + if (!is_float($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotFloat($actual) + { + if (is_float($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isInt($actual) + { + if (!is_int($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotInt($actual) + { + if (is_int($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isNumeric($actual) + { + if (!is_numeric($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotNumeric($actual) + { + if (is_numeric($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isObject($actual) + { + if (!is_object($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotObject($actual) + { + if (is_object($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isResource($actual) + { + if (!is_resource($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotResource($actual) + { + if (is_resource($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isScalar($actual) + { + if (!is_scalar($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotScalar($actual) + { + if (is_scalar($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function isString($actual) + { + if (!is_string($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedButWas)), 0); + } + } + + public function isNotString($actual) + { + if (is_string($actual)) { + throw new TestException(str_replace('{0}', 'null', str_replace('{1}', $this->getDescription($actual), $this->Text->FormatForExpectedNotButWas)), 0); + } + } + + public function fail() + { + throw new TestException($this->Text->Failed, 0); + } + + public function inconclusive() + { + throw new TestException($this->Text->InconclusiveOrNotImplemented, 0); + } + + public function isInstanceOfType($expected, $actual) + { + $actualType = get_class($actual); + if ($expected !== $actualType) { + throw new TestException(str_replace('{0}', $expected, str_replace('{1}', $actualType, $this->Text->FormatForExpectedButWas)), 0); + }; + } + + public function isNotInstanceOfType($expected, $actual) + { + $actualType = get_class($actual); + if ($expected === $actualType) { + throw new TestException(str_replace('{0}', $expected, str_replace('{1}', $actualType, $this->Text->FormatForExpectedNotButWas)), 0); + }; + } + + public function throws($class, $methodName, $args = null) + { + $exception = false; + + try { + if ($args !== null) { + /** @noinspection PhpParamsInspection */ + call_user_func_array(array($class, $methodName), $args); + } else { + $class->{$methodName}(); + } + } catch (\Exception $e) { + $exception = true; + } + + if (!$exception) { + throw new TestException($this->Text->ExpectedExceptionNotThrown, 0); + } + } + + private function getDescription($mixed) + { + if (is_object($mixed)){ + return get_class($mixed); + } else if (is_bool($mixed)){ + return $mixed ? 'true' : 'false'; + } else { + return (string) $mixed; + } + } +} + +class TestException extends \Exception +{ + public function __construct($message = null, $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $trace = $this->getTrace(); + + $this->line = $trace[1]['line']; + $this->file = $trace[1]['file']; + } +} + +interface iOutputTemplate +{ + public function getTemplateType(); + public function get($errors, $results, $text, $duration, $methodCalls); +} + +interface iTestable +{ + public function setUp(); + public function tearDown(); +} + +class HtmlTemplate implements iOutputTemplate +{ + private $Text; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + } + + public function getTemplateType() + { + return TemplateType::Html; + } + + public function get($errors, $results, $text, $duration, $methodCalls) + { + $message = ''; + $failCount = count($errors); + $passCount = count($results); + $methodCallCount = count($methodCalls); + + $currentClass = ''; + if ($failCount > 0) { + $message .= '

' . $text->Test . ' ' . $text->Failed . '

'; + + $message .= '
  • '; + } + $message .= '' . $testClassName . '
  • '; + } else { + $message .= '

    ' . $text->TestPassed . '

    '; + } + + $currentClass = ''; + if ($passCount > 0) { + $message .= '
  • '; + } + $message .= '' . $testClassName . '
  • '; + } + + $message .= '

    ' . $text->MethodCoverage . '

    '; + if ($methodCallCount > 0) { + $message .= ''; + } + + $message .= '

    ' . str_replace('{0}', $duration, $text->FormatForTestRunTook) . '

    '; + + return $this->getTemplateWithMessage($message); + } + + private function getTemplateWithMessage($content) + { + return str_replace('{0}', $content, ' + + + + ' . $this->Text->TestResults . ' + + + + + + +
    +

    ' . $this->Text->EnhanceTestFramework . '

    +
    + +
    + {0} +
    + + + + '); + } +} + +class XmlTemplate implements iOutputTemplate +{ + private $Text; + private $Tab = " "; + private $CR = "\n"; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + } + + public function getTemplateType() + { + return TemplateType::Xml; + } + + public function get($errors, $results, $text, $duration, $methodCalls) + { + $message = ''; + $failCount = count($errors); + + $message .= '' . $this->CR; + if ($failCount > 0) { + $message .= $this->getNode(1, 'result', $text->TestFailed); + } else { + $message .= $this->getNode(1, 'result', $text->TestPassed); + } + + $message .= $this->Tab . '' . $this->CR . + $this->getBadResults($errors) . + $this->getGoodResults($results) . + $this->Tab . '' . $this->CR . + $this->Tab . '' . $this->CR . + $this->getCodeCoverage($methodCalls) . + $this->Tab . '' . $this->CR; + + $message .= $this->getNode(1, 'testRunDuration', $duration) . + '' . $this->CR; + + return $this->getTemplateWithMessage($message); + } + + public function getBadResults($errors) + { + $message = ''; + foreach ($errors as $error) { + $message .= $this->getNode(2, 'fail', $error->Message); + } + return $message; + } + + public function getGoodResults($results) + { + $message = ''; + foreach ($results as $result) { + $message .= $this->getNode(2, 'pass', $result->Message); + } + return $message; + } + + public function getCodeCoverage($methodCalls) + { + $message = ''; + foreach ($methodCalls as $key => $value) { + $message .= $this->buildCodeCoverageMessage($key, $value); + } + return $message; + } + + private function buildCodeCoverageMessage($key, $value) + { + return $this->Tab . $this->Tab . '' . $this->CR . + $this->getNode(3, 'name', str_replace('#', '->', $key)) . + $this->getNode(3, 'timesCalled', $value) . + $this->Tab . $this->Tab . '' . $this->CR; + } + + private function getNode($tabs, $nodeName, $nodeValue) + { + $node = ''; + for ($i = 0; $i < $tabs; ++$i){ + $node .= $this->Tab; + } + $node .= '<' . $nodeName . '>' . $nodeValue . '' . $this->CR; + + return $node; + } + + private function getTemplateWithMessage($content) + { + return str_replace('{0}', $content, '' . "\n" . + '{0}'); + } +} + +class CliTemplate implements iOutputTemplate +{ + private $Text; + private $CR = "\n"; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + } + + public function getTemplateType() + { + return TemplateType::Cli; + } + + public function get($errors, $results, $text, $duration, $methodCalls) + { + $failCount = count($errors); + + $resultMessage = $text->TestPassed . $this->CR; + if ($failCount > 0) { + $resultMessage = $text->TestFailed . $this->CR; + } + + $message = $this->CR . + $resultMessage . + $this->CR . + $this->getBadResults($errors) . + $this->getGoodResults($results) . + $this->CR . + $this->getMethodCoverage($methodCalls) . + $this->CR . + $resultMessage . + str_replace('{0}', $duration, $text->FormatForTestRunTook) . $this->CR; + + return $message; + } + + public function getBadResults($errors) + { + $message = ''; + foreach ($errors as $error) { + $message .= $error->Message . $this->CR; + } + return $message; + } + + public function getGoodResults($results) + { + $message = ''; + foreach ($results as $result) { + $message .= $result->Message . $this->CR; + } + return $message; + } + + public function getMethodCoverage($methodCalls) + { + $message = ''; + foreach ($methodCalls as $key => $value) { + $message .= str_replace('#', '->', $key) . ':' . $value . $this->CR; + } + return $message; + } +} + +class TapTemplate implements iOutputTemplate +{ + private $Text; + private $CR = "\n"; + + public function __construct($language) + { + $this->Text = TextFactory::getLanguageText($language); + } + + public function getTemplateType() + { + return TemplateType::Cli; + } + + public function get($errors, $results, $text, $duration, $methodCalls) + { + $failCount = count($errors); + $passCount = count($results); + $total = $failCount + $passCount; + $count = 0; + + $message = '1..' . $total . $this->CR; + + foreach ($errors as $error) { + ++$count; + $message .= 'not ok ' . $count . ' ' . $error->Message . $this->CR; + } + + foreach ($results as $result) { + ++$count; + $message .= 'ok ' . $count . ' ' . $result->Message . $this->CR; + } + + return $message; + } + +} + +class TemplateFactory +{ + public static function createOutputTemplate($type, $language) + { + switch ($type) { + case TemplateType::Xml: + return new XmlTemplate($language); + break; + case TemplateType::Html: + return new HtmlTemplate($language); + break; + case TemplateType::Cli: + return new CliTemplate($language); + break; + case TemplateType::Tap: + return new TapTemplate($language); + break; + } + + return new HtmlTemplate($language); + } +} + +class TemplateType +{ + const Xml = 0; + const Html = 1; + const Cli = 2; + const Tap = 3; +} + +class Language +{ + const English = 'En'; + const Deutsch = 'De'; +} + +class Localisation +{ + public $Language = Language::English; +} +?> \ No newline at end of file diff --git a/wp_config_snapshots/tests/api_tests/api_tests.php b/wp_config_snapshots/tests/api_tests/api_tests.php new file mode 100644 index 0000000..166fae9 --- /dev/null +++ b/wp_config_snapshots/tests/api_tests/api_tests.php @@ -0,0 +1,130 @@ +'.print_r($var, true).''; + } +} + +class testSnapshot extends WPConfigSnapshotModule +{ + public function type() + { + return 'test_snapshot'; + } + + public function deprecated_check($version_check_object) + { + global $ran_deprecation_check; + $ran_deprecation_check = true; + return $version_check_object; + } + + public function ui_action_hook() + { + return 'test_action'; + } + + public function take_snapshot() + { + return 'test_snapshot'; + } + + public function restore_snapshot($value) + { + global $restored; + $restored = $value; + } +} + +class ConfigSnapshotsAPITests extends \Enhance\TestFixture +{ + + public function setUp() + { + $this->util = new wcsUtil(); + $ts = new testSnapshot(); + WPConfigSnapshotPlugin::instance()->register_snapshot_module($ts); + } + + public function tearDown() + { + + } + + public function it_lets_me_register_snapshot_modules_of_the_right_type() + { + $m = WPConfigSnapshotPlugin::instance()->snapshot_modules(); + \Enhance\Assert::isTrue(array_key_exists('test_snapshot', $m)); + \Enhance\Assert::areIdentical('testSnapshot', get_class($m['test_snapshot'])); + } + + public function it_uses_the_version_check_before_rendering_a_ui() + { + global $ran_deprecation_check; + $ran_deprecation_check = false; + $ts = new testSnapshot(); + $ts->call_render_ui(); + \Enhance\Assert::isTrue($ran_deprecation_check); + } + + public function it_lets_me_pass_it_snapshotted_data_for_storage() + { + delete_option('wp_config_snapshots'); + $option = get_option('wp_config_snapshots'); + \Enhance\Assert::isFalse($option); + $ts = new testSnapshot(); + WPConfigSnapshotPlugin::instance()->take_snapshot($ts->type(), 'first_snapshot'); + WPConfigSnapshotPlugin::instance()->take_snapshot($ts->type(), 'second_snapshot'); + $option = get_option('wp_config_snapshots'); + \Enhance\Assert::areIdentical(1, count($option)); + \Enhance\Assert::areIdentical(2, count($option[$ts->type()])); + \Enhance\Assert::areIdentical($ts->type(), $option[$ts->type()]['first_snapshot']->value()); + \Enhance\Assert::areIdentical($ts->type(), $option[$ts->type()]['second_snapshot']->value()); + } + + public function it_lets_me_tell_it_how_to_restore_a_snapshot() + { + delete_option('wp_config_snapshots'); + $option = get_option('wp_config_snapshots'); + \Enhance\Assert::isFalse($option); + $ts = new testSnapshot(); + WPConfigSnapshotPlugin::instance()->take_snapshot($ts->type(), 'first_snapshot'); + WPConfigSnapshotPlugin::instance()->take_snapshot($ts->type(), 'second_snapshot'); + global $restored; + $restored = false; + WPConfigSnapshotPlugin::instance()->restore_snapshot($ts->type(), 'second_snapshot'); + \Enhance\Assert::areIdentical($ts->type(), $restored); + } +} + +class ConfigSnapshotPluginTests extends \Enhance\TestFixture +{ + public function setUp() + { + delete_option('wp_config_snapshots'); + $this->util = new wcsUtil(); + $ts = new testSnapshot(); + WPConfigSnapshotPlugin::instance()->register_snapshot_module($ts); + } + + public function tearDown() + { + + } + + public function it_sets_the_correct_snapshot_active_when_one_is_restored_or_taken() + { + $ts = new testSnapshot(); + WPConfigSnapshotPlugin::instance()->take_snapshot($ts->type(), 'first_snapshot'); + WPConfigSnapshotPlugin::instance()->take_snapshot($ts->type(), 'second_snapshot'); + $option = get_option('wp_config_snapshots'); + \Enhance\Assert::isTrue($option[$ts->type()]['second_snapshot']->is_active()); + \Enhance\Assert::isFalse($option[$ts->type()]['first_snapshot']->is_active()); + WPConfigSnapshotPlugin::instance()->restore_snapshot($ts->type(), 'first_snapshot'); + \Enhance\Assert::isFalse($option[$ts->type()]['second_snapshot']->is_active()); + \Enhance\Assert::isTrue($option[$ts->type()]['first_snapshot']->is_active()); + } +} \ No newline at end of file diff --git a/wp_config_snapshots/tests/runner.php b/wp_config_snapshots/tests/runner.php new file mode 100644 index 0000000..309ceb1 --- /dev/null +++ b/wp_config_snapshots/tests/runner.php @@ -0,0 +1,13 @@ + +
    +

    Settings Snapshots

    +
    +
    + +

    + + +   + + +

    +

    + + + +

    + +
    +
    +
    + \ No newline at end of file diff --git a/wp_config_snapshots/wp_config_snapshots.php b/wp_config_snapshots/wp_config_snapshots.php new file mode 100644 index 0000000..7bf6847 --- /dev/null +++ b/wp_config_snapshots/wp_config_snapshots.php @@ -0,0 +1,267 @@ +initialize_snapshot_modules(); + $this->require_dependencies(); + $this->add_actions(); + } + + public static function instance() + { + if (!isset(self::$_instance)) self::$_instance = new WPConfigSnapshotPlugin(); + return self::$_instance; + } + + private function initialize_snapshot_modules() + { + $this->_snapshot_modules = array(); + } + + public function register_snapshot_module(WPConfigSnapshotModule $snapshot_module) + { + $this->_snapshot_modules[$snapshot_module->type()] = $snapshot_module; + } + + public function snapshot_modules() + { + return $this->_snapshot_modules; + } + + public function module_is_valid($type) + { + return (is_object($this->_snapshot_modules[$type]) and + get_parent_class($this->_snapshot_modules[$type]) === 'WPConfigSnapshotModule'); + } + + public function require_dependencies() + { + require_once(dirname(__FILE__).'/Stamp.php'); + } + + public function add_actions() + { + add_action('admin_enqueue_scripts', array($this, 'enqueue_js')); + add_action('wp_ajax_add_wp_config_snapshot', array($this, 'take_snapshot_ajax_handler')); + add_action('wp_ajax_restore_wp_config_snapshot', array($this, 'restore_snapshot_ajax_handler')); + } + + public function enqueue_js() + { + wp_enqueue_script('snapshots_js', get_bloginfo('url').'/wp-content/plugins/wp_config_snapshots/js/snapshots.js', array('jquery')); + } + + public function take_snapshot_ajax_handler() + { + $response = $this->take_snapshot($_POST['snapshot_type'], $_POST['snapshot_name']); + die(json_encode($response)); + } + + public function clear_active_snapshot($snapshots) + { + if (!is_array($snapshots)) $snapshots = array(); + foreach($snapshots as $snapshot) + if (get_class($snapshot) === 'WPConfigSnapshot') $snapshot->set_active(false); + return $snapshots; + } + + public function validate_snapshot_request($type=false, $name=false) + { + if (!isset($type) or !$this->module_is_valid($type)) + return new wpcsAjaxResponse('failed', 'Invalid snapshot type provided.'); + if (!isset($name)) return new wpcsAjaxResponse('failed', 'Invalid snapshot name provided.'); + return false; + } + + public function take_snapshot($type=false, $name=false) + { + if ($invalid = $this->validate_snapshot_request($type, $name)) return $invalid; + $option = get_option('wp_config_snapshots'); + $option[$type] = $this->clear_active_snapshot($option[$type]); + $option[$type][$name] = new WPConfigSnapshot($name, $this->_snapshot_modules[$type]->take_snapshot(), true); + update_option('wp_config_snapshots', $option); + return new wpcsAjaxResponse('success'); + } + + public function restore_snapshot_ajax_handler() + { + $response = $this->restore_snapshot($_POST['snapshot_type'], $_POST['snapshot_name']); + die(json_encode($response)); + } + + public function restore_snapshot($type=false, $name=false) + { + if ($invalid = $this->validate_snapshot_request($type, $name)) return $invalid; + $option = get_option('wp_config_snapshots'); + if (!isset($option[$type]) or !isset($option[$type][$name]) or !is_callable(array($option[$type][$name], 'value'))) + return new wpcsAjaxResponse('failed', 'Could not load the specified snapshot from the database.'); + $this->_snapshot_modules[$type]->restore_snapshot($option[$type][$name]->value()); + $option[$type] = $this->clear_active_snapshot($option[$type]); + $option[$type][$name]->set_active(true); + update_option('wp_config_snapshots', $option); + return new wpcsAjaxResponse('success'); + } +} + +class wpcsAjaxResponse +{ + public $status, $message; + + public function __construct($status='', $message='') + { + $this->status = $status; + $this->message = $message; + } +} + +add_action('plugins_loaded', array('WPConfigSnapshotPlugin', 'instance')); + +class WPConfigSnapshotModule +{ + + public function type() + { + return 'base_snapshot'; + } + + public function label() + { + return 'base snapshot'; + } + + public function ui_action_hook() + { + return 'default_ui_hook'; + } + + private function render_ui() + { + $output = new wpsStamp(wpsStamp::load(dirname(__FILE__).'/views/ui.tpl')); + $option = get_option('wp_config_snapshots'); + $select_options = ''; + foreach($option[$this->type()] as $snapshot) + { + $selected = ($snapshot->is_active()) ? "selected='selected'" : ''; + $select_options .= ""; + } + if (trim($select_options) !== '') $output = $output->replace('existing_snapshot_option', $select_options); + $version_check = $this->deprecated_check(new WPConfigSnapshotVersionCheck()); + if ($version_check->is_deprecated()) + $output = $output->replace($version_check->deprecated_ui_block(), $version_check->deprecated_message()); + echo $output->replace('label', $this->label())->replace('type', $this->type()); + } + + public function call_render_ui() + { + return $this->render_ui(); + } + + public function deprecated_check(WPConfigSnapshotVersionCheck $version_check_object) + { + $version_check_object->current_version(1); + $version_check_object->supported_version(1); + $version_check_object->deprecated_message = ''; + return $version_check_object; + } + + public function take_snapshot() + { + return array(); + } + + public function restore_snapshot($snapshot_data) + { + return; + } +} + +class WPConfigSnapshot +{ + private $_name, $_value, $_active; + + public function __construct($name, $value, $active=false) + { + $this->_name = $name; + $this->_value = $value; + $this->_active = $active; + } + + public function name($val=false) + { + if (!$val) return $this->_name; + $this->_name = $val; + } + + public function value($val=false) + { + if (!$val) return $this->_value; + $this->_value = $val; + } + + public function is_active() + { + return $this->_active; + } + + public function set_active($val) + { + $this->_active = $val; + } +} + +class WPConfigSnapshotVersionCheck +{ + private $_current_version, $_supported_version, $_deprecated_message, $_block_ui; + + public function current_version($version) + { + $this->_current_version = $version; + } + + public function supported_version($version) + { + $this->_supported_version = $version; + } + + public function set_block_ui($bool) + { + $this->_block_ui = $bool; + } + + public function block_ui() + { + return $this->_block_ui; + } + + public function deprecated_ui_block() + { + return ($this->_block_ui) ? 'ui_block' : 'dep_note'; + } + + public function deprecated_message($message=false) + { + if (!$message) return $this->_deprecated_message; + $this->_deprecated_message = $message; + } + + public function is_deprecated() + { + return ($this->_current_version === $this->_supported_version) ? '' : $this->_deprecated_message; + } +} \ No newline at end of file